Merge lp:~cjwatson/launchpad/isolate-gpgme into lp:launchpad

Proposed by Colin Watson
Status: Rejected
Rejected by: Colin Watson
Proposed branch: lp:~cjwatson/launchpad/isolate-gpgme
Merge into: lp:launchpad
Diff against target: 197 lines (+60/-17)
2 files modified
lib/lp/services/gpg/handler.py (+54/-14)
lib/lp/testing/gpgkeys/__init__.py (+6/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/isolate-gpgme
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+310906@code.launchpad.net

Commit message

Isolate gpgme from its terminal environment, if any.

Description of the change

I've been having problems for ages with spurious gpgme-related test failures in my local environment that went away when I piped the output through cat, and this was particularly annoying any time I needed to apply pdb to one of them. This isolates things so that that isn't a problem any more.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

This seems like a pretty big hammer to use outside the test suite. The appservers are single-threaded today, but it still feels quite off. Have you examined whether we can force a null pinentry with gpg1? IIRC from my gpg2 work it's certainly possible there -- maybe it's worth just living with the problem until we're on xenial.

Revision history for this message
Colin Watson (cjwatson) wrote :

I'm withdrawing this at least for now, as it doesn't appear to be a problem in xenial test runs.

Unmerged revisions

18270. By Colin Watson

Isolate gpgme from its terminal environment, if any.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/services/gpg/handler.py'
2--- lib/lp/services/gpg/handler.py 2016-11-03 15:07:36 +0000
3+++ lib/lp/services/gpg/handler.py 2016-11-15 17:42:52 +0000
4@@ -5,12 +5,14 @@
5
6 __all__ = [
7 'GPGHandler',
8+ 'isolate_gpgme',
9 'PymeKey',
10 'PymeSignature',
11 'PymeUserId',
12 ]
13
14 import atexit
15+from contextlib import contextmanager
16 import httplib
17 import os
18 import shutil
19@@ -65,6 +67,34 @@
20 """
21
22
23+@contextmanager
24+def isolate_gpgme():
25+ """Isolate gpgme from its terminal environment, if any.
26+
27+ Ensure that stdout is not a tty. gpgme tries to enable interactive
28+ behaviour if this is the case. We never want that, and it can cause
29+ test failures in some environments, so make sure it's not the case while
30+ calling into gpgme.
31+
32+ Make sure that gpgme does not have a DISPLAY.
33+ """
34+ if os.isatty(1):
35+ old_stdout = os.dup(1)
36+ devnull = os.open(os.devnull, os.O_WRONLY)
37+ os.dup2(devnull, 1)
38+ else:
39+ old_stdout = None
40+ old_display = os.environ.pop('DISPLAY', None)
41+ try:
42+ yield
43+ finally:
44+ if old_display is not None:
45+ os.environ['DISPLAY'] = old_display
46+ if old_stdout is not None:
47+ os.dup2(old_stdout, 1)
48+ os.close(old_stdout)
49+
50+
51 @implementer(IGPGHandler)
52 class GPGHandler:
53 """See IGPGHandler."""
54@@ -178,7 +208,8 @@
55
56 # process it
57 try:
58- signatures = ctx.verify(*args)
59+ with isolate_gpgme():
60+ signatures = ctx.verify(*args)
61 except gpgme.GpgmeError as e:
62 error = GPGVerificationError(e.strerror)
63 for attr in ("args", "code", "signatures", "source"):
64@@ -230,7 +261,8 @@
65 context.armor = True
66
67 newkey = StringIO(content)
68- result = context.import_(newkey)
69+ with isolate_gpgme():
70+ result = context.import_(newkey)
71
72 if len(result.imports) == 0:
73 raise GPGKeyNotFoundError(content)
74@@ -264,7 +296,8 @@
75 context = gpgme.Context()
76 context.armor = True
77 newkey = StringIO(content)
78- import_result = context.import_(newkey)
79+ with isolate_gpgme():
80+ import_result = context.import_(newkey)
81
82 secret_imports = [
83 fingerprint
84@@ -277,7 +310,8 @@
85
86 fingerprint, result, status = import_result.imports[0]
87 try:
88- key = context.get_key(fingerprint, True)
89+ with isolate_gpgme():
90+ key = context.get_key(fingerprint, True)
91 except gpgme.GpgmeError:
92 return None
93
94@@ -296,8 +330,9 @@
95 # Only 'utf-8' encoding is supported by gpgme.
96 # See more information at:
97 # http://pyme.sourceforge.net/doc/gpgme/Generating-Keys.html
98- result = context.genkey(
99- signing_only_param % {'name': name.encode('utf-8')})
100+ with isolate_gpgme():
101+ result = context.genkey(
102+ signing_only_param % {'name': name.encode('utf-8')})
103
104 # Right, it might seem paranoid to have this many assertions,
105 # but we have to take key generation very seriously.
106@@ -340,9 +375,10 @@
107 % key.fingerprint)
108
109 # encrypt content
110- ctx.encrypt(
111- [removeSecurityProxy(key.key)], gpgme.ENCRYPT_ALWAYS_TRUST,
112- plain, cipher)
113+ with isolate_gpgme():
114+ ctx.encrypt(
115+ [removeSecurityProxy(key.key)], gpgme.ENCRYPT_ALWAYS_TRUST,
116+ plain, cipher)
117
118 return cipher.getvalue()
119
120@@ -375,7 +411,8 @@
121
122 # Sign the text.
123 try:
124- context.sign(plaintext, signature, mode)
125+ with isolate_gpgme():
126+ context.sign(plaintext, signature, mode)
127 except gpgme.GpgmeError:
128 return None
129
130@@ -393,8 +430,9 @@
131 if type(filter) == unicode:
132 filter = filter.encode('utf-8')
133
134- for key in ctx.keylist(filter, secret):
135- yield PymeKey.newFromGpgmeKey(key)
136+ with isolate_gpgme():
137+ for key in ctx.keylist(filter, secret):
138+ yield PymeKey.newFromGpgmeKey(key)
139
140 def retrieveKey(self, fingerprint):
141 """See IGPGHandler."""
142@@ -549,7 +587,8 @@
143 context = gpgme.Context()
144 # retrive additional key information
145 try:
146- key = context.get_key(fingerprint, False)
147+ with isolate_gpgme():
148+ key = context.get_key(fingerprint, False)
149 except gpgme.GpgmeError:
150 key = None
151
152@@ -597,7 +636,8 @@
153 context = gpgme.Context()
154 context.armor = True
155 keydata = StringIO()
156- context.export(self.fingerprint.encode('ascii'), keydata)
157+ with isolate_gpgme():
158+ context.export(self.fingerprint.encode('ascii'), keydata)
159
160 return keydata.getvalue()
161
162
163=== modified file 'lib/lp/testing/gpgkeys/__init__.py'
164--- lib/lp/testing/gpgkeys/__init__.py 2016-11-03 15:07:36 +0000
165+++ lib/lp/testing/gpgkeys/__init__.py 2016-11-15 17:42:52 +0000
166@@ -1,4 +1,4 @@
167-# Copyright 2009 Canonical Ltd. This software is licensed under the
168+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
169 # GNU Affero General Public License version 3 (see the file LICENSE).
170
171 """OpenPGP keys used for testing.
172@@ -27,11 +27,13 @@
173
174 from lp.registry.interfaces.gpg import IGPGKeySet
175 from lp.registry.interfaces.person import IPersonSet
176+from lp.services.gpg.handler import isolate_gpgme
177 from lp.services.gpg.interfaces import (
178 GPGKeyAlgorithm,
179 IGPGHandler,
180 )
181
182+
183 gpgkeysdir = os.path.join(os.path.dirname(__file__), 'data')
184
185
186@@ -141,9 +143,10 @@
187
188 ctx.passphrase_cb = passphrase_cb
189
190- # Do the deecryption.
191+ # Do the decryption.
192 try:
193- ctx.decrypt(cipher, plain)
194+ with isolate_gpgme():
195+ ctx.decrypt(cipher, plain)
196 except gpgme.GpgmeError:
197 return None
198