Merge lp:~brian-murray/apport/support-ppa-packages into lp:~apport-hackers/apport/trunk

Proposed by Brian Murray
Status: Merged
Merged at revision: 2980
Proposed branch: lp:~brian-murray/apport/support-ppa-packages
Merge into: lp:~apport-hackers/apport/trunk
Diff against target: 503 lines (+274/-20)
6 files modified
AUTHORS (+4/-1)
apport/packaging.py (+5/-1)
apport/sandboxutils.py (+21/-6)
backends/packaging-apt-dpkg.py (+151/-7)
bin/apport-retrace (+4/-1)
test/test_backend_apt_dpkg.py (+89/-4)
To merge this branch: bzr merge lp:~brian-murray/apport/support-ppa-packages
Reviewer Review Type Date Requested Status
Martin Pitt (community) Approve
Review via email: mp+263437@code.launchpad.net

Description of the change

This branch builds upon Tim Lunn's initial work (https://code.launchpad.net/~darkxst/apport/per-ppa-config2/+merge/180972) to add PPA support to apport-retrace.

A list of "origins" is created for packages with a foreign origin. This is then passed to install packages so that the sandbox will be created with proper apt data sources for the PPA. Its also possible to setup an apt data source that will be used when creating the sandbox. For consistency with apt these sources are located in /etc/apt/sources.list.d/, they can either be named the same as the PPA (brian-murray-ppa.list) or with LP-PPA- preceeding the PPA name. This was done since the origin information is listed as LP-PPA-brian-murray-ppa and people may use that when setting up a configuration for apport-retrace. I've also added apt keyring support for any PPAs added.

The following new tests were added to test_backend_apt_dpkg.py:
    def test_create_sources_for_a_named_ppa(self):
    def test_create_sources_for_an_unnamed_ppa(self):
    def test_use_sources_for_a_ppa(self):
    def test_install_package_from_a_ppa(self):

They test the creation of a sources.list entry for two different types of PPAs, using a configured sources.list entry for a PPA, and installing a package from a PPA. Three of the tests are dependent upon two PPAs being available which might be a point of concern.

To post a comment you must log in.
Revision history for this message
Martin Pitt (pitti) wrote :

Thanks Brian! The general idea looks fine; with the restricted "Origin:" fields that we have there's no way around the guesswork.

review: Needs Fixing
2979. By Brian Murray

mess o' modifications based on pitti's feeback

Revision history for this message
Brian Murray (brian-murray) wrote :

Thanks for all the comments, I think I have addressed all of them and the code should look better now.

Revision history for this message
Brian Murray (brian-murray) wrote :

Steve indicated there may be some security concerns about installing any package from Launchpad and running that package's binaries inside gdb. Subsequently, I have made the ability to create apt data sources for PPAs an optional one and set the default value to false. Regardless, if there are sources.list entries in the configdir corresponding to a PPA then packages will be used from those PPAs.

2980. By Brian Murray

Add an option to apport-retrace (and pass it along) so that apt sources data is only created for PPAs if requested.

Revision history for this message
Steve Langasek (vorlon) wrote :

Security concerns, and also infrastructure load concerns. I don't believe we've made any committment to providing retracing resources for crashes of packages originating from arbitrary ppas.

2981. By Brian Murray

Fix origins re, pass log_timestamps to make_sandbox, fix a bug in origin_path existence checking.

2982. By Brian Murray

explicitly close the results from urlopen

Revision history for this message
Martin Pitt (pitti) wrote :

A couple of inline replies to the previous review round. I'll review the new code in a separate comment.

Revision history for this message
Martin Pitt (pitti) wrote :

Thanks for the updates! Another round, then this should be perfect.

review: Needs Fixing
2983. By Brian Murray

make testing for gpg keys less fragile

2984. By Brian Murray

address pitti's feedback and simplify how using origins is specified

Revision history for this message
Brian Murray (brian-murray) wrote :

Okay, I think I've addressed everything, except for your concern regarding sources.list entries which I've commented on in-line.

Revision history for this message
Martin Pitt (pitti) wrote :

Responding to sources.list parsing.

Revision history for this message
Martin Pitt (pitti) wrote :

Some tiny nitpicks left, which I'd also be happy to do myself during merge. Do you want to change the sources.list parsing as discussed? If not I'm okay with leaving it like that and cleaning this up in a separate MP. Thanks!

2985. By Brian Murray

address final nitpicks

Revision history for this message
Brian Murray (brian-murray) wrote :

I've fixed the nitpicks and would prefer to sort out the sources.list parsing in a separate merge proposal.

Revision history for this message
Martin Pitt (pitti) wrote :
Download full text (3.3 KiB)

The last commit changed the wrong len(components), I fixed that and added a NEWS entry.

The tests fail with PYTHON=python2, with this:

======================================================================
ERROR: test_create_sources_for_a_named_ppa (__main__.T)
Add sources.list entries for a named PPA.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test/test_backend_apt_dpkg.py", line 987, in test_create_sources_for_a_named_ppa
    'ubuntu', 'trusty', origins=[ppa])
  File "backends/packaging-apt-dpkg.py", line 1368, in _build_apt_sandbox
    ppa_info = apport.packaging.json_request(ppa_archive_url)
  File "/home/martin/ubuntu/apport/trunk/apport/packaging_impl.py", line 284, in json_request
    return json.loads(content)
  File "/usr/lib/python2.7/json/__init__.py", line 338, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python2.7/json/decoder.py", line 366, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python2.7/json/decoder.py", line 384, in raw_decode
    raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

I checked json_request, and content == "Object: <Person at 0x2b0b217fc8d0 daisy (Daisy Bachmann)>, name: u'+archive'" which is indeed not JSON.

In the "good" python 3 case the URL is https//api.launchpad.net/devel/~daisy-pluckers/+archive/ubuntu/daisy-seeds, in the "bad" python 2 case it is https://api.launchpad.net/devel/~daisy/+archive/ubuntu/pluckers-daisy-seeds, which is obviously scrambled.

This is indeed weird:

$ python3 -c 'from urllib.request import urlopen; print(urlopen("https://api.launchpad.net/devel
/~daisy/+archive/ubuntu/pluckers-daisy-seeds").read())'
[...]
urllib.error.HTTPError: HTTP Error 404: Not Found

$ python -c 'from urllib import urlopen; print(urlopen("https://api.launchpad.net/devel/~daisy/+archive/ubuntu/pluckers-daisy-seeds").read())'
Object: <Person at 0x2b4df96bcbd0 daisy (Daisy Bachmann)>, name: u'+archive'

However, .getcode() says "404" in python 2, so this needs to be checked separately. I added that now.

There is one failure left with Python 2:

======================================================================
ERROR: test_use_sources_for_a_ppa (__main__.T)
Use a sources.list.d file for a PPA.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test/test_backend_apt_dpkg.py", line 1028, in test_use_sources_for_a_ppa
    'ubuntu', 'trusty', origins=['LP-PPA-%s' % ppa])
  File "backends/packaging-apt-dpkg.py", line 1372, in _build_apt_sandbox
    ppa_info = apport.packaging.json_request(ppa_archive_url)
  File "/home/martin/ubuntu/apport/trunk/apport/packaging_impl.py", line 282, in json_request
    return json.loads(content)
  File "/usr/lib/python2.7/json/__init__.py", line 338, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python2.7/json/decoder.py", line 366, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python2.7/json/decoder.py", line 384, in raw_decode
    raise ValueError("No JSON object could be decoded")
Valu...

Read more...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'AUTHORS'
2--- AUTHORS 2011-11-09 08:40:10 +0000
3+++ AUTHORS 2015-07-13 22:32:23 +0000
4@@ -1,7 +1,7 @@
5 Copyright:
6 ---------
7 General:
8- Copyright (C) 2006 - 2011 Canonical Ltd.
9+ Copyright (C) 2006 - 2015 Canonical Ltd.
10
11 backends/packaging_rpm.py:
12 Copyright (C) 2007 Red Hat Inc.
13@@ -32,3 +32,6 @@
14
15 Kees Cook <kees.cook@canonical.com>:
16 Various fixes, additional GDB output, SEGV parser.
17+
18+Brian Murray <brian.murray@canonical.com>:
19+ Various fixes, installation of packages from Launchpad and PPAs.
20
21=== modified file 'apport/packaging.py'
22--- apport/packaging.py 2015-07-02 16:13:23 +0000
23+++ apport/packaging.py 2015-07-13 22:32:23 +0000
24@@ -192,7 +192,8 @@
25
26 def install_packages(self, rootdir, configdir, release, packages,
27 verbose=False, cache_dir=None,
28- permanent_rootdir=False, architecture=None):
29+ permanent_rootdir=False, architecture=None,
30+ origins=None):
31 '''Install packages into a sandbox (for apport-retrace).
32
33 In order to work without any special permissions and without touching
34@@ -221,6 +222,9 @@
35 the given architecture (as specified in a report's "Architecture"
36 field). If not given it defaults to the host system's architecture.
37
38+ If origins is given, the sandbox will be created with apt data sources
39+ for foreign origins.
40+
41 Return a string with outdated packages, or None if all packages were
42 installed.
43
44
45=== modified file 'apport/sandboxutils.py'
46--- apport/sandboxutils.py 2015-06-11 05:49:39 +0000
47+++ apport/sandboxutils.py 2015-07-13 22:32:23 +0000
48@@ -10,7 +10,7 @@
49 # option) any later version. See http://www.gnu.org/copyleft/gpl.html for
50 # the full text of the license.
51
52-import atexit, os, os.path, shutil, tempfile
53+import atexit, os, os.path, re, shutil, tempfile
54 import apport
55
56
57@@ -103,7 +103,8 @@
58
59
60 def make_sandbox(report, config_dir, cache_dir=None, sandbox_dir=None,
61- extra_packages=[], verbose=False, log_timestamps=False):
62+ extra_packages=[], verbose=False, log_timestamps=False,
63+ dynamic_origins=False):
64 '''Build a sandbox with the packages that belong to a particular report.
65
66 This downloads and unpacks all packages from the report's Package and
67@@ -140,8 +141,14 @@
68 are not derived from the report.
69
70 If verbose is True (False by default), this will write some additional
71- logging to stdout. If log_timestamps is True, these log messages will be
72- prefixed with the current time.
73+ logging to stdout.
74+
75+ If log_timestamps is True, these log messages will be prefixed with the
76+ current time.
77+
78+ If dynamic_origins is True (False by default), the sandbox will be built
79+ with packages from foreign origins that appear in the report's
80+ Packages:/Dependencies:.
81
82 Return a tuple (sandbox_dir, cache_dir, outdated_msg).
83 '''
84@@ -179,12 +186,20 @@
85 if config_dir == 'system':
86 config_dir = None
87
88+ origins = None
89+ if dynamic_origins:
90+ pkg_list = report.get('Package', '') + '\n' + report.get('Dependencies', '')
91+ m = re.compile('\[origin: ([a-zA-Z0-9][a-zA-Z0-9\+\.\-]+)\]')
92+ origins = set(m.findall(pkg_list))
93+ if origins:
94+ apport.log("Origins: %s" % origins)
95+
96 # unpack packages, if any, using cache and sandbox
97 try:
98 outdated_msg = apport.packaging.install_packages(
99 sandbox_dir, config_dir, report['DistroRelease'], pkgs,
100 verbose, cache_dir, permanent_rootdir,
101- architecture=report.get('Architecture'))
102+ architecture=report.get('Architecture'), origins=origins)
103 except SystemError as e:
104 apport.fatal(str(e))
105
106@@ -210,7 +225,7 @@
107 outdated_msg += apport.packaging.install_packages(
108 sandbox_dir, config_dir, report['DistroRelease'], pkgs,
109 verbose, cache_dir, permanent_rootdir,
110- architecture=report.get('Architecture'))
111+ architecture=report.get('Architecture'), origins=origins)
112 except SystemError as e:
113 apport.fatal(str(e))
114
115
116=== modified file 'backends/packaging-apt-dpkg.py'
117--- backends/packaging-apt-dpkg.py 2015-07-02 16:13:23 +0000
118+++ backends/packaging-apt-dpkg.py 2015-07-13 22:32:23 +0000
119@@ -16,6 +16,8 @@
120 import hashlib
121 import json
122
123+from contextlib import closing
124+
125 import warnings
126 warnings.filterwarnings('ignore', 'apt API not stable yet', FutureWarning)
127 import apt
128@@ -24,9 +26,10 @@
129 from urllib import urlopen, quote, unquote
130 (pickle, urlopen, quote, unquote) # pyflakes
131 URLError = IOError
132+ HTTPError = IOError
133 except ImportError:
134 # python 3
135- from urllib.error import URLError
136+ from urllib.error import URLError, HTTPError
137 from urllib.request import urlopen
138 from urllib.parse import quote, unquote
139 import pickle
140@@ -47,6 +50,7 @@
141 self._virtual_mapping_obj = None
142 self._launchpad_base = 'https://api.launchpad.net/devel'
143 self._archive_url = self._launchpad_base + '/%s/main_archive'
144+ self._ppa_archive_url = self._launchpad_base + '/~%(user)s/+archive/%(distro)s/%(ppaname)s'
145
146 def __del__(self):
147 try:
148@@ -88,14 +92,15 @@
149 self._apt_cache = apt.Cache(rootdir='/')
150 return self._apt_cache
151
152- def _sandbox_cache(self, aptroot, apt_sources, fetchProgress):
153+ def _sandbox_cache(self, aptroot, apt_sources, fetchProgress, distro_name, release_codename, origins):
154 '''Build apt sandbox and return apt.Cache(rootdir=) (initialized lazily).
155
156 Clear the package selection on subsequent calls.
157 '''
158 self._apt_cache = None
159 if not self._sandbox_apt_cache:
160- self._build_apt_sandbox(aptroot, apt_sources)
161+ self._build_apt_sandbox(aptroot, apt_sources, distro_name,
162+ release_codename, origins)
163 rootdir = os.path.abspath(aptroot)
164 self._sandbox_apt_cache = apt.Cache(rootdir=rootdir)
165 try:
166@@ -660,7 +665,8 @@
167
168 def install_packages(self, rootdir, configdir, release, packages,
169 verbose=False, cache_dir=None,
170- permanent_rootdir=False, architecture=None):
171+ permanent_rootdir=False, architecture=None,
172+ origins=None):
173 '''Install packages into a sandbox (for apport-retrace).
174
175 In order to work without any special permissions and without touching
176@@ -689,6 +695,9 @@
177 the given architecture (as specified in a report's "Architecture"
178 field). If not given it defaults to the host system's architecture.
179
180+ If origins is given, the sandbox will be created with apt data sources
181+ for foreign origins.
182+
183 Return a string with outdated packages, or None if all packages were
184 installed.
185
186@@ -747,9 +756,14 @@
187 else:
188 fetchProgress = apt.progress.base.AcquireProgress()
189 if not tmp_aptroot:
190- cache = self._sandbox_cache(aptroot, apt_sources, fetchProgress)
191+ cache = self._sandbox_cache(aptroot, apt_sources, fetchProgress,
192+ self.get_distro_name(),
193+ self.current_release_codename,
194+ origins)
195 else:
196- self._build_apt_sandbox(aptroot, apt_sources)
197+ self._build_apt_sandbox(aptroot, apt_sources,
198+ self.get_distro_name(),
199+ self.current_release_codename, origins)
200 cache = apt.Cache(rootdir=os.path.abspath(aptroot))
201 try:
202 cache.update(fetchProgress)
203@@ -1203,7 +1217,67 @@
204 return None
205
206 @classmethod
207- def _build_apt_sandbox(klass, apt_root, apt_sources):
208+ def create_ppa_source_from_origin(klass, origin, distro, release_codename):
209+ '''For an origin from a Launchpad PPA create sources.list content.
210+
211+ distro is the distribution for which content is being created e.g.
212+ ubuntu.
213+
214+ release_codename is the codename of the release for which content is
215+ being created e.g. trusty.
216+
217+ Return a string containing content suitable for writing to a sources.list
218+ file, or None if the origin is not a Launchpad PPA.
219+ '''
220+
221+ if origin.startswith("LP-PPA-"):
222+ components = origin.split("-")[2:]
223+ # If the PPA is unnamed, it will not appear in origin information
224+ # but is named ppa in Launchpad.
225+ try_ppa = True
226+ if len(components) == 1:
227+ components.append('ppa')
228+ try_ppa = False
229+
230+ index = 1
231+ while components[index:]:
232+ # For an origin we can't tell where the user name ends and the
233+ # PPA name starts, so split on each "-" until we find a PPA
234+ # that exists.
235+ user = str.join('-', components[0:index])
236+ ppa_name = str.join('-', components[index:len(components)])
237+ try:
238+ with closing(urlopen(apport.packaging._ppa_archive_url %
239+ {'user': user, 'distro': distro,
240+ 'ppaname': ppa_name})) as response:
241+ response.read()
242+ except (URLError, HTTPError):
243+ index += 1
244+ if index == len(components):
245+ if try_ppa:
246+ components.append('ppa')
247+ try_ppa = False
248+ index = 2
249+ else:
250+ user = None
251+ continue
252+ break
253+ if user and ppa_name:
254+ ppa_line = 'deb http://ppa.launchpad.net/%s/%s/%s %s main' % \
255+ (user, ppa_name, distro, release_codename)
256+ debug_url = 'http://ppa.launchpad.net/%s/%s/%s/dists/%s/main/debug' % \
257+ (user, ppa_name, distro, release_codename)
258+ try:
259+ with closing(urlopen(debug_url)) as response:
260+ response.read()
261+ add_debug = ' main/debug'
262+ except (URLError, HTTPError):
263+ add_debug = ''
264+ return ppa_line + add_debug + '\ndeb-src' + ppa_line[3:] + '\n'
265+ return None
266+
267+ @classmethod
268+ def _build_apt_sandbox(klass, apt_root, apt_sources, distro_name, release_codename, origins):
269 # pre-create directories, to avoid apt.Cache() printing "creating..."
270 # messages on stdout
271 if not os.path.exists(os.path.join(apt_root, 'var', 'lib', 'apt')):
272@@ -1225,6 +1299,46 @@
273 with open(os.path.join(apt_root, 'etc', 'apt', 'sources.list'), 'w') as dest:
274 dest.write(src.read())
275
276+ if origins:
277+ source_list_content = ''
278+ # map an origin to a Launchpad username and PPA name
279+ origin_data = {}
280+ for origin in origins:
281+ # apport's report format uses unknown for packages w/o an origin
282+ if origin == 'unknown':
283+ continue
284+ origin_path = None
285+ if os.path.isdir(apt_sources + '.d'):
286+ # check to see if there is a sources.list file for the origin,
287+ # if there isn't try using a sources.list file w/o LP-PPA-
288+ origin_path = os.path.join(apt_sources + '.d', origin + '.list')
289+ if not os.path.exists(origin_path) and 'LP-PPA' in origin:
290+ origin_path = os.path.join(apt_sources + '.d',
291+ origin.strip('LP-PPA-') + '.list')
292+ if not os.path.exists(origin_path):
293+ origin_path = None
294+ elif not os.path.exists(origin_path):
295+ origin_path = None
296+ if origin_path:
297+ with open(origin_path) as src_ext:
298+ source_list_content = src_ext.read()
299+ else:
300+ source_list_content = klass.create_ppa_source_from_origin(origin, distro_name, release_codename)
301+ if source_list_content:
302+ with open(os.path.join(apt_root, 'etc', 'apt',
303+ 'sources.list.d', origin + '.list'), 'a') as dest:
304+ dest.write(source_list_content)
305+ for line in source_list_content.splitlines():
306+ if line.startswith('#'):
307+ continue
308+ if 'ppa.launchpad.net' not in line:
309+ continue
310+ user = line.split()[1].split('/')[3]
311+ ppa = line.split()[1].split('/')[4]
312+ origin_data[origin] = (user, ppa)
313+ else:
314+ apport.warning("Could not find or create source config for %s" % origin)
315+
316 # install apt keyrings; prefer the ones from the config dir, fall back
317 # to system
318 trusted_gpg = os.path.join(os.path.dirname(apt_sources), 'trusted.gpg')
319@@ -1244,6 +1358,36 @@
320 else:
321 os.makedirs(trusted_d)
322
323+ # install apt keyrings for PPAs
324+ if origins and source_list_content:
325+ for origin, (ppa_user, ppa_name) in origin_data.items():
326+ ppa_archive_url = apport.packaging._ppa_archive_url % \
327+ {'user': quote(ppa_user), 'distro': distro_name,
328+ 'ppaname': quote(ppa_name)}
329+ ppa_info = apport.packaging.json_request(ppa_archive_url)
330+ if not ppa_info:
331+ continue
332+ try:
333+ signing_key_fingerprint = ppa_info['signing_key_fingerprint']
334+ except IndexError:
335+ apport.warning("Error: can't find signing_key_fingerprint at %s"
336+ % ppa_archive_url)
337+ continue
338+ argv = ['gpg', '--no-options',
339+ '--no-default-keyring',
340+ '--no-auto-check-trustdb',
341+ '--keyring',
342+ os.path.join(trusted_d,
343+ '%s.gpg' % origin),
344+ ]
345+ argv += ['--quiet', '--batch',
346+ '--keyserver', 'hkp://keyserver.ubuntu.com:80/',
347+ '--recv', signing_key_fingerprint]
348+ if subprocess.call(argv) != 0:
349+ apport.warning('Unable to import key for %s' %
350+ ppa_archive_url)
351+ pass
352+
353 @classmethod
354 def _deb_version(klass, pkg):
355 '''Return the version of a .deb file'''
356
357=== modified file 'bin/apport-retrace'
358--- bin/apport-retrace 2015-05-29 21:53:42 +0000
359+++ bin/apport-retrace 2015-07-13 22:32:23 +0000
360@@ -53,6 +53,8 @@
361 help=_('Report download/install progress when installing packages into sandbox'))
362 argparser.add_argument('--timestamps', action='store_true',
363 help=_('Prepend timestamps to log messages, for batch operation'))
364+ argparser.add_argument('--dynamic-origins', action='store_true',
365+ help=_('Create and use third-party repositories from origins specified in reports'))
366 argparser.add_argument('-C', '--cache', metavar='DIR',
367 help=_('Cache directory for packages downloaded in the sandbox'))
368 argparser.add_argument('--sandbox-dir', metavar='DIR',
369@@ -295,7 +297,8 @@
370 if options.sandbox:
371 sandbox, cache, outdated_msg = apport.sandboxutils.make_sandbox(
372 report, options.sandbox, options.cache, options.sandbox_dir,
373- options.extra_package, options.verbose)
374+ options.extra_package, options.verbose, log_timestamps,
375+ options.dynamic_origins)
376 else:
377 sandbox = None
378 outdated_msg = None
379
380=== modified file 'test/test_backend_apt_dpkg.py'
381--- test/test_backend_apt_dpkg.py 2015-06-29 13:31:02 +0000
382+++ test/test_backend_apt_dpkg.py 2015-07-13 22:32:23 +0000
383@@ -953,7 +953,8 @@
384 self._setup_foonux_config()
385 out_dir = os.path.join(self.workdir, 'out')
386 os.mkdir(out_dir)
387- impl._build_apt_sandbox(self.rootdir, os.path.join(self.configdir, 'Foonux 1.2', 'sources.list'))
388+ impl._build_apt_sandbox(self.rootdir, os.path.join(self.configdir, 'Foonux 1.2', 'sources.list'),
389+ 'ubuntu', 'trusty', origins=None)
390 res = impl.get_source_tree('base-files', out_dir, sandbox=self.rootdir,
391 apt_update=True)
392 self.assertTrue(os.path.isdir(os.path.join(res, 'debian')))
393@@ -967,7 +968,8 @@
394 self._setup_foonux_config()
395 out_dir = os.path.join(self.workdir, 'out')
396 os.mkdir(out_dir)
397- impl._build_apt_sandbox(self.rootdir, os.path.join(self.configdir, 'Foonux 1.2', 'sources.list'))
398+ impl._build_apt_sandbox(self.rootdir, os.path.join(self.configdir, 'Foonux 1.2', 'sources.list'),
399+ 'ubuntu', 'trusty', origins=None)
400 res = impl.get_source_tree('debian-installer', out_dir, version='20101020ubuntu318.16',
401 sandbox=self.rootdir, apt_update=True)
402 self.assertTrue(os.path.isdir(os.path.join(res, 'debian')))
403@@ -976,8 +978,86 @@
404 self.assertTrue(res.endswith('/debian-installer-20101020ubuntu318.16'),
405 'unexpected version: ' + res.split('/')[-1])
406
407- def _setup_foonux_config(self, updates=False, release='trusty'):
408- '''Set up directories and configuration for install_packages()'''
409+ @unittest.skipUnless(_has_internet(), 'online test')
410+ def test_create_sources_for_a_named_ppa(self):
411+ '''Add sources.list entries for a named PPA.'''
412+ ppa = 'LP-PPA-daisy-pluckers-daisy-seeds'
413+ self._setup_foonux_config()
414+ impl._build_apt_sandbox(self.rootdir, os.path.join(self.configdir, 'Foonux 1.2', 'sources.list'),
415+ 'ubuntu', 'trusty', origins=[ppa])
416+ with open(os.path.join(self.rootdir, 'etc', 'apt', 'sources.list.d', ppa + '.list')) as f:
417+ sources = f.read().splitlines()
418+ self.assertIn('deb http://ppa.launchpad.net/daisy-pluckers/daisy-seeds/ubuntu trusty main main/debug', sources)
419+ self.assertIn('deb-src http://ppa.launchpad.net/daisy-pluckers/daisy-seeds/ubuntu trusty main', sources)
420+
421+ d = subprocess.Popen(['gpg', '--no-options', '--no-default-keyring',
422+ '--no-auto-check-trustdb', '--trust-model',
423+ 'always', '--batch', '--list-keys', '--keyring',
424+ os.path.join(self.rootdir, 'etc', 'apt', 'trusted.gpg.d', 'LP-PPA-daisy-pluckers-daisy-seeds.gpg')],
425+ stdout=subprocess.PIPE)
426+ apt_keys = d.communicate()[0].decode()
427+ assert d.returncode == 0
428+ self.assertIn('Launchpad PPA for Daisy Pluckers', apt_keys)
429+
430+ @unittest.skipUnless(_has_internet(), 'online test')
431+ def test_create_sources_for_an_unnamed_ppa(self):
432+ '''Add sources.list entries for an unnamed PPA.'''
433+ ppa = 'LP-PPA-brian-murray'
434+ self._setup_foonux_config()
435+ impl._build_apt_sandbox(self.rootdir, os.path.join(self.configdir, 'Foonux 1.2', 'sources.list'),
436+ 'ubuntu', 'trusty', origins=[ppa])
437+ with open(os.path.join(self.rootdir, 'etc', 'apt', 'sources.list.d', ppa + '.list')) as f:
438+ sources = f.read().splitlines()
439+ self.assertIn('deb http://ppa.launchpad.net/brian-murray/ppa/ubuntu trusty main', sources)
440+ self.assertIn('deb-src http://ppa.launchpad.net/brian-murray/ppa/ubuntu trusty main', sources)
441+
442+ d = subprocess.Popen(['gpg', '--no-options', '--no-default-keyring',
443+ '--no-auto-check-trustdb', '--trust-model',
444+ 'always', '--batch', '--list-keys', '--keyring',
445+ os.path.join(self.rootdir, 'etc', 'apt', 'trusted.gpg.d', 'LP-PPA-brian-murray.gpg')],
446+ stdout=subprocess.PIPE)
447+ apt_keys = d.communicate()[0].decode()
448+ assert d.returncode == 0
449+ self.assertIn('Launchpad PPA for Brian Murray', apt_keys)
450+
451+ def test_use_sources_for_a_ppa(self):
452+ '''Use a sources.list.d file for a PPA.'''
453+ ppa = 'fooser-bar-ppa'
454+ self._setup_foonux_config(ppa=True)
455+ impl._build_apt_sandbox(self.rootdir, os.path.join(self.configdir, 'Foonux 1.2', 'sources.list'),
456+ 'ubuntu', 'trusty', origins=['LP-PPA-%s' % ppa])
457+ with open(os.path.join(self.rootdir, 'etc', 'apt', 'sources.list.d', ppa + '.list')) as f:
458+ sources = f.read().splitlines()
459+ self.assertIn('deb http://ppa.launchpad.net/fooser/bar-ppa/ubuntu trusty main main/debug', sources)
460+ self.assertIn('deb-src http://ppa.launchpad.net/fooser/bar-ppa/ubuntu trusty main', sources)
461+
462+ @unittest.skipUnless(_has_internet(), 'online test')
463+ def test_install_package_from_a_ppa(self):
464+ '''Install a package from a PPA.'''
465+ ppa = 'LP-PPA-brian-murray'
466+ self._setup_foonux_config()
467+ obsolete = impl.install_packages(self.rootdir, self.configdir, 'Foonux 1.2',
468+ [('apport',
469+ '2.14.1-0ubuntu3.7~ppa4')
470+ ], False, self.cachedir, origins=[ppa])
471+
472+ self.assertEqual(obsolete, '')
473+
474+ def sandbox_ver(pkg):
475+ with gzip.open(os.path.join(self.rootdir, 'usr/share/doc', pkg,
476+ 'changelog.Debian.gz')) as f:
477+ return f.readline().decode().split()[1][1:-1]
478+
479+ self.assertEqual(sandbox_ver('apport'),
480+ '2.14.1-0ubuntu3.7~ppa4')
481+
482+ def _setup_foonux_config(self, updates=False, release='trusty', ppa=False):
483+ '''Set up directories and configuration for install_packages()
484+
485+ If ppa is True, then a sources.list file for a PPA will be created
486+ in sources.list.d used to test copying of a sources.list file to a
487+ sandbox.
488+ '''
489
490 self.cachedir = os.path.join(self.workdir, 'cache')
491 self.rootdir = os.path.join(self.workdir, 'root')
492@@ -994,6 +1074,11 @@
493 f.write('deb http://archive.ubuntu.com/ubuntu/ %s-updates main\n' % release)
494 f.write('deb-src http://archive.ubuntu.com/ubuntu/ %s-updates main\n' % release)
495 f.write('deb http://ddebs.ubuntu.com/ %s-updates main\n' % release)
496+ if ppa:
497+ os.mkdir(os.path.join(self.configdir, 'Foonux 1.2', 'sources.list.d'))
498+ with open(os.path.join(self.configdir, 'Foonux 1.2', 'sources.list.d', 'fooser-bar-ppa.list'), 'w') as f:
499+ f.write('deb http://ppa.launchpad.net/fooser/bar-ppa/ubuntu %s main main/debug\n' % release)
500+ f.write('deb-src http://ppa.launchpad.net/fooser/bar-ppa/ubuntu %s main\n' % release)
501 os.mkdir(os.path.join(self.configdir, 'Foonux 1.2', 'armhf'))
502 with open(os.path.join(self.configdir, 'Foonux 1.2', 'armhf', 'sources.list'), 'w') as f:
503 f.write('deb http://ports.ubuntu.com/ %s main\n' % release)

Subscribers

People subscribed via source and target branches