162
142
sourcepackagerelease = source_publication.sourcepackagerelease
163
143
changesfile_object = sourcepackagerelease.upload_changesfile
145
# No changesfile available, cannot close bugs.
146
if changesfile_object is None:
165
149
close_bugs_for_sourcepackagerelease(
166
sourcepackagerelease, changesfile_object, since_version,
167
upload_distroseries=source_publication.distroseries)
170
def close_bugs_for_sourcepackagerelease(source_release, changesfile_object,
172
upload_distroseries=None):
150
sourcepackagerelease, changesfile_object)
153
def close_bugs_for_sourcepackagerelease(source_release, changesfile_object):
173
154
"""Close bugs for a given source.
175
156
Given a `ISourcePackageRelease` and a corresponding changesfile object,
176
157
close bugs mentioned in the changesfile in the context of the source.
178
If changesfile_object is None and since_version is supplied,
179
close all the bugs in changelog entries made after that version and up
180
to and including the source_release's version. It does this by parsing
181
the changelog on the sourcepackagerelease. This could be extended in
182
the future to deal with the changes file as well but there is no
183
requirement to do so right now.
185
if since_version and source_release.changelog:
186
bugs_to_close = get_bugs_from_changelog_entry(
187
source_release, since_version=since_version)
188
elif changesfile_object:
189
bugs_to_close = get_bugs_from_changes_file(changesfile_object)
159
bugs_to_close = get_bugs_from_changes_file(changesfile_object)
193
161
# No bugs to be closed by this upload, move on.
194
162
if not bugs_to_close:
228
"""Policy describing what kinds of archives to operate on."""
230
def __init__(self, logger):
233
def getTargetArchives(self, distribution):
234
"""Get target archives of the right sort for `distribution`."""
235
raise NotImplemented("getTargetArchives")
237
def describeArchive(self, archive):
238
"""Return textual description for `archive` in this script run."""
239
raise NotImplemented("describeArchive")
241
def postprocessSuccesses(self, queue_ids):
242
"""Optionally, post-process successfully processed queue items.
244
:param queue_ids: An iterable of `PackageUpload` ids that were
245
successfully processed.
249
class PPATargetPolicy(TargetPolicy):
250
"""Target policy for PPA archives."""
252
def getTargetArchives(self, distribution):
253
"""See `TargetPolicy`."""
254
return distribution.getPendingAcceptancePPAs()
256
def describeArchive(self, archive):
257
"""See `TargetPolicy`."""
258
return archive.archive_url
261
class CopyArchiveTargetPolicy(TargetPolicy):
262
"""Target policy for copy archives."""
264
def getTargetArchives(self, distribution):
265
"""See `TargetPolicy`."""
266
return getUtility(IArchiveSet).getArchivesForDistribution(
267
distribution, purposes=[ArchivePurpose.COPY])
269
def describeArchive(self, archive):
270
"""See `TargetPolicy`."""
271
return archive.displayname
274
class DistroTargetPolicy(TargetPolicy):
275
"""Target policy for distro archives."""
277
def getTargetArchives(self, distribution):
278
"""See `TargetPolicy`."""
279
return distribution.all_distro_archives
281
def describeArchive(self, archive):
282
"""See `TargetPolicy`."""
283
return archive.purpose.title
285
def postprocessSuccesses(self, queue_ids):
286
"""See `TargetPolicy`."""
287
self.logger.debug("Closing bugs.")
288
for queue_id in queue_ids:
289
queue_item = getUtility(IPackageUploadSet).get(queue_id)
290
close_bugs_for_queue_item(queue_item)
293
class ProcessAccepted(LaunchpadCronScript):
192
class ProcessAccepted(LaunchpadScript):
294
193
"""Queue/Accepted processor.
296
195
Given a distribution to run on, obtains all the queue items for the
313
205
help="Whether to treat this as a dry-run or not.")
315
207
self.parser.add_option(
316
'-D', '--derived', action="store_true", dest="derived",
317
default=False, help="Process all Ubuntu-derived distributions.")
319
self.parser.add_option(
320
"--ppa", action="store_true", dest="ppa", default=False,
208
"--ppa", action="store_true",
209
dest="ppa", metavar="PPA", default=False,
321
210
help="Run only over PPA archives.")
323
212
self.parser.add_option(
324
"--copy-archives", action="store_true", dest="copy_archives",
213
"--copy-archives", action="store_true",
214
dest="copy_archives", metavar="COPY_ARCHIVES",
325
215
default=False, help="Run only over COPY archives.")
328
"""Commit transaction (unless in dry-run mode)."""
329
if self.options.dryrun:
330
self.logger.debug("Skipping commit: dry-run mode.")
334
def findNamedDistro(self, distro_name):
335
"""Find the `Distribution` called `distro_name`."""
336
self.logger.debug("Finding distribution %s.", distro_name)
337
distro = getUtility(IDistributionSet).getByName(distro_name)
339
raise LaunchpadScriptFailure(
340
"Distribution '%s' not found." % distro_name)
343
def findTargetDistros(self):
344
"""Find the distribution(s) to process, based on arguments."""
345
if self.options.derived:
346
return getUtility(IDistributionSet).getDerivedDistributions()
348
return [self.findNamedDistro(self.args[0])]
350
def validateArguments(self):
351
"""Validate command-line arguments."""
218
def lockfilename(self):
219
"""Override LaunchpadScript's lock file name."""
220
return "/var/lock/launchpad-upload-queue.lock"
223
"""Entry point for a LaunchpadScript."""
224
if len(self.args) != 1:
226
"Need to be given exactly one non-option argument. "
227
"Namely the distribution to process.")
352
230
if self.options.ppa and self.options.copy_archives:
353
raise OptionValueError(
354
232
"Specify only one of copy archives or ppa archives.")
355
if self.options.derived:
356
if len(self.args) != 0:
357
raise OptionValueError(
358
"Can't combine --derived with a distribution name.")
360
if len(self.args) != 1:
361
raise OptionValueError(
362
"Need to be given exactly one non-option argument. "
363
"Namely the distribution to process.")
365
def makeTargetPolicy(self):
366
"""Pick and instantiate a `TargetPolicy` based on given options."""
368
policy_class = PPATargetPolicy
369
elif self.options.copy_archives:
370
policy_class = CopyArchiveTargetPolicy
372
policy_class = DistroTargetPolicy
373
return policy_class(self.logger)
375
def processQueueItem(self, queue_item):
376
"""Attempt to process `queue_item`.
378
This method swallows exceptions that occur while processing the
381
:param queue_item: A `PackageUpload` to process.
382
:return: True on success, or False on failure.
384
self.logger.debug("Processing queue item %d" % queue_item.id)
386
queue_item.realiseUpload(self.logger)
388
message = "Failure processing queue_item %d" % queue_item.id
389
properties = [('error-explanation', message)]
390
request = ScriptRequest(properties)
391
ErrorReportingUtility().raising(sys.exc_info(), request)
392
self.logger.error('%s (%s)', message, request.oopsid)
396
"Successfully processed queue item %d", queue_item.id)
399
def processForDistro(self, distribution, target_policy):
400
"""Process all queue items for a distribution.
402
Commits between items, except in dry-run mode.
404
:param distribution: The `Distribution` to process queue items for.
405
:param target_policy: The applicable `TargetPolicy`.
406
:return: A list of all successfully processed items' ids.
235
distro_name = self.args[0]
408
237
processed_queue_ids = []
409
for archive in target_policy.getTargetArchives(distribution):
410
description = target_policy.describeArchive(archive)
411
for distroseries in distribution.series:
413
self.logger.debug("Processing queue for %s %s" % (
414
distroseries.name, description))
416
queue_items = distroseries.getPackageUploads(
417
status=PackageUploadStatus.ACCEPTED, archive=archive)
418
for queue_item in queue_items:
419
if self.processQueueItem(queue_item):
420
processed_queue_ids.append(queue_item.id)
421
# Commit even on error; we may have altered the
422
# on-disk archive, so the partial state must
425
return processed_queue_ids
428
"""Entry point for a LaunchpadScript."""
429
self.validateArguments()
430
target_policy = self.makeTargetPolicy()
432
for distro in self.findTargetDistros():
433
queue_ids = self.processForDistro(distro, target_policy)
435
target_policy.postprocessSuccesses(queue_ids)
239
self.logger.debug("Finding distribution %s." % distro_name)
240
distribution = getUtility(IDistributionSet).getByName(distro_name)
241
if distribution is None:
242
raise LaunchpadScriptFailure(
243
"Distribution '%s' not found." % distro_name)
245
# target_archives is a tuple of (archive, description).
248
(archive, archive.archive_url)
249
for archive in distribution.getPendingAcceptancePPAs()]
250
elif self.options.copy_archives:
251
copy_archives = getUtility(
252
IArchiveSet).getArchivesForDistribution(
253
distribution, purposes=[ArchivePurpose.COPY])
255
(archive, archive.displayname)
256
for archive in copy_archives]
259
(archive, archive.purpose.title)
260
for archive in distribution.all_distro_archives]
262
for archive, description in target_archives:
263
for distroseries in distribution.series:
265
self.logger.debug("Processing queue for %s %s" % (
266
distroseries.name, description))
268
queue_items = distroseries.getPackageUploads(
269
status=PackageUploadStatus.ACCEPTED, archive=archive)
270
for queue_item in queue_items:
272
"Processing queue item %d" % queue_item.id)
274
queue_item.realiseUpload(self.logger)
276
message = "Failure processing queue_item %d" % (
278
properties = [('error-explanation', message)]
279
request = ScriptRequest(properties)
280
error_utility = ErrorReportingUtility()
281
error_utility.raising(sys.exc_info(), request)
282
self.logger.error('%s (%s)' % (message,
286
"Successfully processed queue item %d" %
288
processed_queue_ids.append(queue_item.id)
289
# Commit even on error; we may have altered the
290
# on-disk archive, so the partial state must
294
if not self.options.dryrun:
297
self.logger.debug("Dry Run mode.")
299
if not self.options.ppa and not self.options.copy_archives:
300
self.logger.debug("Closing bugs.")
301
close_bugs(processed_queue_ids)
303
if not self.options.dryrun:
439
307
self.logger.debug("Rolling back any remaining transactions.")