Merge lp:~cjwatson/launchpad/cveimport-requests into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18682
Proposed branch: lp:~cjwatson/launchpad/cveimport-requests
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/bugs-remote-finders-requests
Diff against target: 234 lines (+83/-19)
6 files modified
constraints.txt (+1/-0)
lib/lp/bugs/scripts/cveimport.py (+17/-5)
lib/lp/services/config/schema-lazr.conf (+3/-0)
lib/lp/services/tests/test_timeout.py (+33/-2)
lib/lp/services/timeout.py (+28/-12)
setup.py (+1/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/cveimport-requests
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+347201@code.launchpad.net

Commit message

Convert update-cve to urlfetch with explicit proxy configuration.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)
Revision history for this message
Colin Watson (cjwatson) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'constraints.txt'
--- constraints.txt 2018-05-31 10:23:03 +0000
+++ constraints.txt 2018-06-05 01:51:40 +0000
@@ -336,6 +336,7 @@
336PyYAML==3.10336PyYAML==3.10
337rabbitfixture==0.3.6337rabbitfixture==0.3.6
338requests==2.7.0338requests==2.7.0
339requests-file==1.4.3
339requests-toolbelt==0.6.2340requests-toolbelt==0.6.2
340responses==0.9.0341responses==0.9.0
341scandir==1.7342scandir==1.7
342343
=== modified file 'lib/lp/bugs/scripts/cveimport.py'
--- lib/lp/bugs/scripts/cveimport.py 2016-08-16 15:33:02 +0000
+++ lib/lp/bugs/scripts/cveimport.py 2018-06-05 01:51:40 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""A set of functions related to the ability to parse the XML CVE database,4"""A set of functions related to the ability to parse the XML CVE database,
@@ -10,9 +10,9 @@
10import gzip10import gzip
11import StringIO11import StringIO
12import time12import time
13import urllib2
14import xml.etree.cElementTree as cElementTree13import xml.etree.cElementTree as cElementTree
1514
15import requests
16from zope.component import getUtility16from zope.component import getUtility
17from zope.event import notify17from zope.event import notify
18from zope.interface import implementer18from zope.interface import implementer
@@ -31,6 +31,10 @@
31 LaunchpadCronScript,31 LaunchpadCronScript,
32 LaunchpadScriptFailure,32 LaunchpadScriptFailure,
33 )33 )
34from lp.services.timeout import (
35 override_timeout,
36 urlfetch,
37 )
3438
3539
36CVEDB_NS = '{http://cve.mitre.org/cve/downloads/1.0}'40CVEDB_NS = '{http://cve.mitre.org/cve/downloads/1.0}'
@@ -209,14 +213,22 @@
209 elif self.options.cveurl is not None:213 elif self.options.cveurl is not None:
210 self.logger.info("Downloading CVE database from %s..." %214 self.logger.info("Downloading CVE database from %s..." %
211 self.options.cveurl)215 self.options.cveurl)
216 proxies = {}
217 if config.launchpad.http_proxy:
218 proxies['http'] = config.launchpad.http_proxy
219 proxies['https'] = config.launchpad.http_proxy
212 try:220 try:
213 url = urllib2.urlopen(self.options.cveurl)221 with override_timeout(config.cveupdater.timeout):
214 except (urllib2.HTTPError, urllib2.URLError):222 # Command-line options are trusted, so allow file://
223 # URLs to ease testing.
224 response = urlfetch(
225 self.options.cveurl, proxies=proxies, allow_file=True)
226 except requests.RequestException:
215 raise LaunchpadScriptFailure(227 raise LaunchpadScriptFailure(
216 'Unable to connect for CVE database %s'228 'Unable to connect for CVE database %s'
217 % self.options.cveurl)229 % self.options.cveurl)
218230
219 cve_db_gz = url.read()231 cve_db_gz = response.content
220 self.logger.info("%d bytes downloaded." % len(cve_db_gz))232 self.logger.info("%d bytes downloaded." % len(cve_db_gz))
221 cve_db = gzip.GzipFile(233 cve_db = gzip.GzipFile(
222 fileobj=StringIO.StringIO(cve_db_gz)).read()234 fileobj=StringIO.StringIO(cve_db_gz)).read()
223235
=== modified file 'lib/lp/services/config/schema-lazr.conf'
--- lib/lp/services/config/schema-lazr.conf 2018-05-31 12:47:31 +0000
+++ lib/lp/services/config/schema-lazr.conf 2018-06-05 01:51:40 +0000
@@ -490,6 +490,9 @@
490# datatype: string490# datatype: string
491cve_db_url: https://cve.mitre.org/data/downloads/allitems.xml.gz491cve_db_url: https://cve.mitre.org/data/downloads/allitems.xml.gz
492492
493# datatype: integer
494timeout: 30
495
493496
494[database]497[database]
495# Connection strings, format as per the PQconnectdb connection string as498# Connection strings, format as per the PQconnectdb connection string as
496499
=== modified file 'lib/lp/services/tests/test_timeout.py'
--- lib/lp/services/tests/test_timeout.py 2018-06-05 01:31:47 +0000
+++ lib/lp/services/tests/test_timeout.py 2018-06-05 01:51:40 +0000
@@ -15,14 +15,22 @@
15import threading15import threading
16import xmlrpclib16import xmlrpclib
1717
18from fixtures import MonkeyPatch18from fixtures import (
19 MonkeyPatch,
20 TempDir,
21 )
19from requests import Response22from requests import Response
20from requests.exceptions import (23from requests.exceptions import (
21 ConnectionError,24 ConnectionError,
22 InvalidSchema,25 InvalidSchema,
23 )26 )
24from testtools.matchers import MatchesStructure27from testtools.matchers import (
28 ContainsDict,
29 Equals,
30 MatchesStructure,
31 )
2532
33from lp.services.osutils import write_file
26from lp.services.timeout import (34from lp.services.timeout import (
27 default_timeout,35 default_timeout,
28 get_default_timeout_function,36 get_default_timeout_function,
@@ -368,6 +376,17 @@
368 self.assertEqual(376 self.assertEqual(
369 "No connection adapters were found for '%s'" % url, str(e))377 "No connection adapters were found for '%s'" % url, str(e))
370378
379 def test_urlfetch_does_not_support_file_urls_by_default(self):
380 """urlfetch() does not support file urls by default."""
381 set_default_timeout_function(lambda: 1)
382 self.addCleanup(set_default_timeout_function, None)
383 test_path = self.useFixture(TempDir()).join('file')
384 write_file(test_path, '')
385 url = 'file://' + test_path
386 e = self.assertRaises(InvalidSchema, urlfetch, url)
387 self.assertEqual(
388 "No connection adapters were found for '%s'" % url, str(e))
389
371 def test_urlfetch_no_proxy_by_default(self):390 def test_urlfetch_no_proxy_by_default(self):
372 """urlfetch does not use a proxy by default."""391 """urlfetch does not use a proxy by default."""
373 self.pushConfig('launchpad', http_proxy='http://proxy.example:3128/')392 self.pushConfig('launchpad', http_proxy='http://proxy.example:3128/')
@@ -399,6 +418,18 @@
399 {scheme: proxy for scheme in ('http', 'https')},418 {scheme: proxy for scheme in ('http', 'https')},
400 fake_send.calls[0][1]['proxies'])419 fake_send.calls[0][1]['proxies'])
401420
421 def test_urlfetch_supports_file_urls_if_allow_file(self):
422 """urlfetch() supports file urls if explicitly asked to do so."""
423 set_default_timeout_function(lambda: 1)
424 self.addCleanup(set_default_timeout_function, None)
425 test_path = self.useFixture(TempDir()).join('file')
426 write_file(test_path, 'Success.')
427 url = 'file://' + test_path
428 self.assertThat(urlfetch(url, allow_file=True), MatchesStructure(
429 status_code=Equals(200),
430 headers=ContainsDict({'Content-Length': Equals(8)}),
431 content=Equals('Success.')))
432
402 def test_xmlrpc_transport(self):433 def test_xmlrpc_transport(self):
403 """ Another use case for timeouts is communicating with external434 """ Another use case for timeouts is communicating with external
404 systems using XMLRPC. In order to allow timeouts using XMLRPC we435 systems using XMLRPC. In order to allow timeouts using XMLRPC we
405436
=== modified file 'lib/lp/services/timeout.py'
--- lib/lp/services/timeout.py 2018-06-05 01:31:47 +0000
+++ lib/lp/services/timeout.py 2018-06-05 01:51:40 +0000
@@ -40,6 +40,7 @@
40 )40 )
41from requests.packages.urllib3.exceptions import ClosedPoolError41from requests.packages.urllib3.exceptions import ClosedPoolError
42from requests.packages.urllib3.poolmanager import PoolManager42from requests.packages.urllib3.poolmanager import PoolManager
43from requests_file import FileAdapter
43from six import reraise44from six import reraise
4445
45from lp.services.config import config46from lp.services.config import config
@@ -318,25 +319,38 @@
318class URLFetcher:319class URLFetcher:
319 """Object fetching remote URLs with a time out."""320 """Object fetching remote URLs with a time out."""
320321
321 @staticmethod322 def __init__(self):
322 def _makeSession(trust_env=None):323 self.session = None
323 session = Session()324
325 @with_timeout(cleanup='cleanup')
326 def fetch(self, url, trust_env=None, use_proxy=False, allow_file=False,
327 **request_kwargs):
328 """Fetch the URL using a custom HTTP handler supporting timeout.
329
330 :param url: The URL to fetch.
331 :param trust_env: If not None, set the session's trust_env to this
332 to determine whether it fetches proxy configuration from the
333 environment.
334 :param use_proxy: If True, use Launchpad's configured proxy.
335 :param allow_file: If True, allow file:// URLs. (Be careful to only
336 pass this if the URL is trusted.)
337 :param request_kwargs: Additional keyword arguments passed on to
338 `Session.request`.
339 """
340 self.session = Session()
324 if trust_env is not None:341 if trust_env is not None:
325 session.trust_env = trust_env342 self.session.trust_env = trust_env
326 # Mount our custom adapters.343 # Mount our custom adapters.
327 session.mount("https://", CleanableHTTPAdapter())344 self.session.mount("https://", CleanableHTTPAdapter())
328 session.mount("http://", CleanableHTTPAdapter())345 self.session.mount("http://", CleanableHTTPAdapter())
329 return session346 if allow_file:
347 self.session.mount("file://", FileAdapter())
330348
331 @with_timeout(cleanup='cleanup')
332 def fetch(self, url, trust_env=None, use_proxy=False, **request_kwargs):
333 """Fetch the URL using a custom HTTP handler supporting timeout."""
334 request_kwargs.setdefault("method", "GET")349 request_kwargs.setdefault("method", "GET")
335 if use_proxy and config.launchpad.http_proxy:350 if use_proxy and config.launchpad.http_proxy:
336 request_kwargs.setdefault("proxies", {})351 request_kwargs.setdefault("proxies", {})
337 request_kwargs["proxies"]["http"] = config.launchpad.http_proxy352 request_kwargs["proxies"]["http"] = config.launchpad.http_proxy
338 request_kwargs["proxies"]["https"] = config.launchpad.http_proxy353 request_kwargs["proxies"]["https"] = config.launchpad.http_proxy
339 self.session = self._makeSession(trust_env=trust_env)
340 response = self.session.request(url=url, **request_kwargs)354 response = self.session.request(url=url, **request_kwargs)
341 response.raise_for_status()355 response.raise_for_status()
342 # Make sure the content has been consumed before returning.356 # Make sure the content has been consumed before returning.
@@ -345,7 +359,9 @@
345359
346 def cleanup(self):360 def cleanup(self):
347 """Reset the connection when the operation timed out."""361 """Reset the connection when the operation timed out."""
348 self.session.close()362 if self.session is not None:
363 self.session.close()
364 self.session = None
349365
350366
351def urlfetch(url, trust_env=None, **request_kwargs):367def urlfetch(url, trust_env=None, **request_kwargs):
352368
=== modified file 'setup.py'
--- setup.py 2018-05-31 10:23:03 +0000
+++ setup.py 2018-06-05 01:51:40 +0000
@@ -207,6 +207,7 @@
207 'PyYAML',207 'PyYAML',
208 'rabbitfixture',208 'rabbitfixture',
209 'requests',209 'requests',
210 'requests-file',
210 'requests-toolbelt',211 'requests-toolbelt',
211 'responses',212 'responses',
212 'scandir',213 'scandir',