Merge ubuntu-debuginfod:prepare-getter-for-ppa-support into ubuntu-debuginfod:master

Proposed by Sergio Durigan Junior
Status: Merged
Merged at revision: db1a90892665343c30a8bb6aadb1f39b0bea9104
Proposed branch: ubuntu-debuginfod:prepare-getter-for-ppa-support
Merge into: ubuntu-debuginfod:master
Diff against target: 313 lines (+128/-53)
2 files modified
ddebgetter.py (+111/-50)
debuggetter.py (+17/-3)
Reviewer Review Type Date Requested Status
Athos Ribeiro (community) Approve
Lena Voytek (community) Approve
Bryce Harrington Pending
Canonical Server Reporter Pending
Review via email: mp+438422@code.launchpad.net

Description of the change

This MP implements improvements/adjustments that are needed in the "getter" classes for the upcoming PPA support.

I spent some time trying to come up with a logical way to split the several commits that I have made these past weeks, but they're just too intertwined. So instead, I chose to (try to) split the changes into the "getter" and "poller" areas of the code.

Hopefully this is easy enough to review. As I said in the beginning, this is all prep work; it doesn't introduce any new user-visible feature just yet.

To post a comment you must log in.
Revision history for this message
Lena Voytek (lvoytek) wrote :

The code looks good to me! I added some comments on currently unused variables and a function comment but otherwise I approve

review: Approve
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Thanks for the review, Lena.

I addressed your comment about having a docstring for do_process_request. I'll wait until tomorrow before I merge this code in order to give Bryce and Athos a chance to review it, too.

Revision history for this message
Bryce Harrington (bryce) wrote :

Thanks, looking now.

Btw, would be good to rename the master branch to 'main' when you get a chance.

Revision history for this message
Athos Ribeiro (athos-ribeiro) wrote :

Thanks Sergio!

This LGTM. I added a few comments inline, but they are mostly nitpicks. Feel free to address or ignore them and merge :)

review: Approve
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Thanks, Athos. I'm addressing your points now.

Revision history for this message
Bryce Harrington (bryce) wrote :

Comments inline.

I think you could safely squash 3f7ceb2 and b9d6063 since they're closely related (their changelog messages are almost the same).

Revision history for this message
Sergio Durigan Junior (sergiodj) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/ddebgetter.py b/ddebgetter.py
2index 7dc951f..08dcf5e 100644
3--- a/ddebgetter.py
4+++ b/ddebgetter.py
5@@ -30,46 +30,89 @@ from git.exc import GitCommandError
6 from debuggetter import DebugGetter, DebugGetterRetry, DEFAULT_MIRROR_DIR
7
8
9+def _validate_generic_request(request):
10+ """Validate a request coming from Celery.
11+
12+ :param dict[str, str] request: The dictionary containing the
13+ request to be validated."""
14+ if request is None:
15+ raise TypeError("Invalid request (None)")
16+ if request.get("architecture") is None:
17+ raise TypeError("No 'architecture' in request")
18+
19+def validate_ddeb_request(request):
20+ """Validate a request to fetch a ddeb from the main archive.
21+
22+ :param dict[str, str] request: The dictionary containing the
23+ request to be validated."""
24+ _validate_generic_request(request)
25+ if request.get("ddeb_url") is None:
26+ raise TypeError("No 'ddeb_url' in request")
27+ if request["architecture"] == "source":
28+ raise ValueError("Wrong request: source fetch")
29+
30+def validate_source_code_request(request):
31+ """Validate a request to fetch a source package from the main
32+ archive.
33+
34+ :param dict[str, str] request: The dictionary containing the
35+ request to be validated."""
36+ _validate_generic_request(request)
37+ if request.get("source_urls") is None:
38+ raise TypeError("No 'source_urls' in request")
39+ if request["architecture"] != "source":
40+ raise ValueError("Wrong request: ddeb fetch")
41+
42+
43 class DdebGetter(DebugGetter):
44 """Get (fetch) a ddeb."""
45
46- def __init__(self, mirror_dir=DEFAULT_MIRROR_DIR):
47+ def __init__(self, subdir="ddebs", mirror_dir=DEFAULT_MIRROR_DIR):
48 """Initialize the object.
49
50 See DebugGetter's __init__ for an explanation of the arguments."""
51- super().__init__(subdir="ddebs", mirror_dir=mirror_dir)
52+ super().__init__(subdir=subdir, mirror_dir=mirror_dir)
53
54 def process_request(self, request):
55 """Process a request, usually coming from Celery.
56
57- :param request: The dictionary containing the information
58- necessary to fetch this ddeb.
59- :type request: dict(str : str)"""
60- if request is None:
61- raise TypeError("Invalid request (None)")
62- if request.get("ddeb_url") is None:
63- raise TypeError("No 'ddeb_url' in request")
64- if request.get("architecture") is None:
65- raise TypeError("No 'architecture' in request")
66- if request["architecture"] == "source":
67- raise ValueError("Wrong request: source fetch")
68+ :param dict[str, str] request: The dictionary containing the
69+ information necessary to fetch this ddeb."""
70+ validate_ddeb_request(request)
71+ self.do_process_request(request)
72+
73+ def do_process_request(self, request, credentials=None):
74+ """Perform the actual processing of the request.
75
76+ :param dict[str, str] request: The dictionary containing the
77+ information necessary to fetch this ddeb.
78+
79+ :param requests_oauthlib.OAuth1 credentials: The credentials
80+ to be used when downloading the artifact from Launchpad.
81+ Default is None, which means anonymous."""
82 self._logger.debug(f"Processing request to download ddeb: {request}")
83 self._download_ddeb(
84- request["source_package"], request["component"], request["ddeb_url"]
85+ request["source_package"],
86+ request["component"],
87+ request["ddeb_url"],
88+ credentials=credentials,
89 )
90
91- def _download_ddeb(self, source_package, component, ddeb_url):
92+ def _download_ddeb(self, source_package, component, ddeb_url, credentials=None):
93 """Download a ddeb associated with a package.
94
95 :param str source_package: Source package name.
96
97 :param str component: Source package component.
98
99- :param str ddeb_url: The ddeb URL."""
100+ :param str ddeb_url: The ddeb URL.
101+
102+ :param requests_oauthlib.OAuth1 credentials: The credentials
103+ to be used when downloading the artifact from Launchpad.
104+ Default is None, which means anonymous."""
105 savepath = self._make_savepath(source_package, component)
106 self._logger.debug(f"Downloading '{ddeb_url}' into '{savepath}'")
107- self._download_from_lp(ddeb_url, savepath)
108+ self._download_from_lp(ddeb_url, savepath, credentials=credentials)
109
110
111 # The function below was taken from git-ubuntu. We need to make
112@@ -106,30 +149,34 @@ def adjust_tar_filepath(tarinfo):
113 class DdebSourceCodeGetter(DebugGetter):
114 """Get (fetch) the source code associated with a ddeb."""
115
116- def __init__(self, mirror_dir="/srv/debug-mirror"):
117- super().__init__(mirror_dir=mirror_dir, subdir="ddebs")
118+ def __init__(self, subdir="ddebs", mirror_dir=DEFAULT_MIRROR_DIR):
119+ super().__init__(subdir=subdir, mirror_dir=mirror_dir)
120
121 def process_request(self, request):
122 """Process a request, usually coming from Celery.
123
124- :param request: The dictionary containing the information
125- necessary to fetch this source code.
126- :type request: dict(str : str)"""
127- if request is None:
128- raise TypeError("Invalid request (None)")
129- if request.get("source_urls") is None:
130- raise TypeError("No 'source_urls' in request")
131- if request.get("architecture") is None:
132- raise TypeError("No 'architecture' in request")
133- if request["architecture"] != "source":
134- raise ValueError("Wrong request: ddeb fetch")
135+ :param dict[str, str] request: The dictionary containing the
136+ information necessary to fetch this source code."""
137+ validate_source_code_request(request)
138+ self.do_process_request(request)
139+
140+ def do_process_request(self, request, fallback_to_git=True, credentials=None):
141+ """Perform the actual processing of the request.
142+
143+ :param dict[str, str] request: The dictionary containing the
144+ information necessary to fetch this source code.
145
146+ :param requests_oauthlib.OAuth1 credentials: The credentials
147+ to be used when downloading the artifact from Launchpad.
148+ Default is None, which means anonymous."""
149 self._logger.debug(f"Processing request to download source code: {request}")
150- self._process_source(
151+ self._download_source_code(
152 request["source_package"],
153 request["version"],
154 request["component"],
155 request["source_urls"],
156+ fallback_to_git=fallback_to_git,
157+ credentials=credentials,
158 )
159
160 def _download_source_code_from_git(self, source_package, version, filepath):
161@@ -181,9 +228,8 @@ class DdebSourceCodeGetter(DebugGetter):
162 return True
163
164 def _download_source_code_from_dsc(
165- self, source_package, version, filepath, source_urls
166+ self, source_package, version, filepath, source_urls, credentials=None
167 ):
168-
169 """Download the source code using the .dsc file.
170
171 :param str source_package: Source package name.
172@@ -197,13 +243,17 @@ class DdebSourceCodeGetter(DebugGetter):
173 package. This is usually the list returned by the
174 sourceFileUrls() Launchpad API call.
175
176+ :param requests_oauthlib.OAuth1 credentials: The credentials
177+ to be used when downloading the artifact from Launchpad.
178+ Default is None, which means anonymous.
179+
180 :rtype: bool
181 This method returns True when the operation succeeds, or False
182 *iff* the "dpkg-source -x" command fails to run. Otherwise,
183 this function will throw an exception."""
184 with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as source_dir:
185 for url in source_urls:
186- self._download_from_lp(url, source_dir)
187+ self._download_from_lp(url, source_dir, credentials=credentials)
188
189 dscfile = None
190 for f in os.listdir(source_dir):
191@@ -245,7 +295,16 @@ class DdebSourceCodeGetter(DebugGetter):
192
193 return True
194
195- def _download_source_code(self, source_package, version, component, source_urls):
196+ def _download_source_code(
197+ self,
198+ source_package,
199+ version,
200+ component,
201+ source_urls,
202+ fallback_to_git=True,
203+ credentials=None,
204+ ):
205+
206 """Download the source code for a package.
207
208 :param str source_package: Source package name.
209@@ -254,7 +313,15 @@ class DdebSourceCodeGetter(DebugGetter):
210
211 :param str component: Source package component.
212
213- :param list source_urls: List of source file URLS."""
214+ :param list source_urls: List of source file URLS.
215+
216+ :param boolean fallback_to_git: Whether we should try to fetch
217+ the source code using git if the regular approach (via
218+ dget) fails. Default to True.
219+
220+ :param requests_oauthlib.OAuth1 credentials: The credentials
221+ to be used when downloading the artifact from Launchpad.
222+ Default is None, which means anonymous."""
223 savepath = self._make_savepath(source_package, component)
224 txzfilepath = os.path.join(savepath, f"{source_package}-{version}.tar.xz")
225 if os.path.exists(txzfilepath):
226@@ -262,14 +329,20 @@ class DdebSourceCodeGetter(DebugGetter):
227 return
228
229 if self._download_source_code_from_dsc(
230- source_package, version, txzfilepath, source_urls
231+ source_package,
232+ version,
233+ txzfilepath,
234+ source_urls,
235+ credentials=credentials
236 ):
237 self._logger.info(
238 f"Downloaded source code from dsc for '{source_package}-{version}' as '{txzfilepath}'"
239 )
240 return
241
242- if self._download_source_code_from_git(source_package, version, txzfilepath):
243+ if fallback_to_git and self._download_source_code_from_git(
244+ source_package, version, txzfilepath
245+ ):
246 self._logger.info(
247 f"Downloaded source code from git for '{source_package}-{version}' as '{txzfilepath}'"
248 )
249@@ -279,15 +352,3 @@ class DdebSourceCodeGetter(DebugGetter):
250 # Launchpad, let's raise an exception signalling that we'd
251 # like to retry the task.
252 raise DebugGetterRetry()
253-
254- def _process_source(self, source_package, version, component, source_urls):
255- """Process the request to fetch the source code.
256-
257- :param str source_package: Source package name.
258-
259- :param str version: Source package version.
260-
261- :param str component: Source package component.
262-
263- :param list source_urls: List of source URLs."""
264- self._download_source_code(source_package, version, component, source_urls)
265diff --git a/debuggetter.py b/debuggetter.py
266index e6b1de0..c96bfc4 100644
267--- a/debuggetter.py
268+++ b/debuggetter.py
269@@ -92,7 +92,7 @@ class DebugGetter:
270 f"Could not create '{dst}' atomically: same file already exists"
271 )
272
273- def _download_from_lp(self, url, savepath):
274+ def _download_from_lp(self, url, savepath, credentials=None):
275 """Download a file from Launchpad.
276
277 This method tries to download the file in chunks into a
278@@ -102,7 +102,11 @@ class DebugGetter:
279 :param str url: The URL that should be downloaded.
280
281 :param str savepath: The full path (minus the filename) where
282- the file should be saved."""
283+ the file should be saved.
284+
285+ :param requests_oauthlib.OAuth1 credentials: The credentials
286+ to be used when downloading the artifact from Launchpad.
287+ Default is None, which means anonymous."""
288 filepath = os.path.join(savepath, os.path.basename(parse.urlparse(url).path))
289 if os.path.exists(filepath):
290 self._logger.debug(f"'{filepath}' exists, doing nothing")
291@@ -110,7 +114,9 @@ class DebugGetter:
292
293 try:
294 with requests.Session() as s:
295- with s.get(url, allow_redirects=True, timeout=10, stream=True) as r:
296+ with s.get(
297+ url, allow_redirects=True, timeout=10, stream=True, auth=credentials
298+ ) as r:
299 r.raise_for_status()
300 with tempfile.NamedTemporaryFile(mode="wb") as tmpfile:
301 shutil.copyfileobj(r.raw, tmpfile)
302@@ -127,3 +133,11 @@ class DebugGetter:
303 raise DebugGetterTimeout()
304
305 self._logger.debug(f"Saved '{filepath}'")
306+
307+ @property
308+ def subdir(self):
309+ return self._subdir
310+
311+ @subdir.setter
312+ def subdir(self, subdir):
313+ self._subdir = subdir

Subscribers

People subscribed via source and target branches

to all changes: