3
# Copyright 2009 Canonical Ltd. This software is licensed under the
4
# GNU Affero General Public License version 3 (see the file LICENSE).
6
# This script runs through the set of Debbugs watches, and tries to
7
# syncronise each of those to the malone bug which is watching it.
16
from zope.component import getUtility
18
from canonical.database.constants import UTC_NOW
19
from lp.app.errors import NotFoundError
20
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
21
from lp.bugs.interfaces.bug import IBugSet
22
from lp.bugs.interfaces.bugtask import (
26
from lp.bugs.interfaces.bugwatch import IBugWatchSet
27
from lp.bugs.interfaces.cve import ICveSet
28
from lp.bugs.scripts import debbugs
29
from lp.services.scripts.base import (LaunchpadCronScript,
30
LaunchpadScriptFailure)
31
from lp.services.messages.interfaces.message import (
37
# setup core values and defaults
38
debbugs_location_default = '/srv/bugs-mirror.debian.org/'
41
class DebWatchUpdater(LaunchpadCronScript):
42
loglevel = logging.WARNING
44
def add_my_options(self):
45
self.parser.add_option(
46
'--max', action='store', type='int', dest='max',
47
default=None, help="The maximum number of bugs to synchronise.")
48
self.parser.add_option('--debbugs', action='store', type='string',
50
default=debbugs_location_default,
51
help="The location of your debbugs database.")
54
if not os.path.exists(
55
os.path.join(self.options.debbugs, 'index/index.db')):
56
raise LaunchpadScriptFailure('%s is not a debbugs db.'
57
% self.options.debbugs)
60
self.debbugs_db = debbugs.Database(self.options.debbugs)
64
self.logger.info('Done!')
69
self.logger.info('Finding existing debbugs watches...')
70
debbugs_tracker = getUtility(ILaunchpadCelebrities).debbugs
71
debwatches = debbugs_tracker.watches
73
previousimportset = set([b.remotebug for b in debwatches])
75
'%d debbugs previously imported.' % len(previousimportset))
77
target_watches = [watch for watch in debwatches if watch.needscheck]
79
'%d debbugs watches to syncronise.' % len(target_watches))
81
self.logger.info('Sorting bug watches...')
82
target_watches.sort(key=lambda a: a.remotebug)
84
self.logger.info('Syncing bug watches...')
85
for watch in target_watches:
86
if self.sync_watch(watch):
90
if changedcounter >= self.options.max:
91
self.logger.info('Synchronised %d bugs!' % changedcounter)
94
def sync_watch(self, watch):
95
# keep track of whether or not something changed
97
# find the bug in malone
98
malone_bug = watch.bug
99
# find the bug in debbugs
100
debian_bug = self.debbugs_db[int(watch.remotebug)]
101
bugset = getUtility(IBugSet)
102
bugtaskset = getUtility(IBugTaskSet)
103
bugwatchset = getUtility(IBugWatchSet)
104
messageset = getUtility(IMessageSet)
105
debian = getUtility(ILaunchpadCelebrities).debian
106
debbugs_tracker = getUtility(ILaunchpadCelebrities).debbugs
108
# make sure we have tasks for all the debian package linkages, and
109
# also make sure we have updated their status and severity
111
for packagename in debian_bug.packagelist():
113
srcpkgname = debian.guessPublishedSourcePackageName(
115
except NotFoundError:
116
self.logger.error(sys.exc_value)
118
search_params = BugTaskSearchParams(user=None, bug=malone_bug,
119
sourcepackagename=srcpkgname)
120
search_params.setDistribution(debian)
121
bugtasks = bugtaskset.search(search_params)
122
if len(bugtasks) == 0:
123
# we need a new task to link the bug to the debian package
124
self.logger.info('Linking %d and debian %s' % (
125
malone_bug.id, srcpkgname.name))
126
# XXX: kiko 2007-02-03:
127
# This code is completely untested and broken.
128
bugtask = malone_bug.addTask(
129
owner=malone_bug.owner, distribution=debian,
130
sourcepackagename=srcpkgname)
131
bugtask.bugwatch = watch
134
assert len(bugtasks) == 1, 'Should only find a single task'
135
bugtask = bugtasks[0]
136
status = bugtask.status
137
if status != bugtask.setStatusFromDebbugs(debian_bug.status):
139
severity = bugtask.severity
140
if severity != bugtask.setSeverityFromDebbugs(
141
debian_bug.severity):
144
known_msg_ids = set([msg.rfc822msgid for msg in malone_bug.messages])
146
for raw_msg in debian_bug.comments:
148
# parse it so we can extract the message id easily
149
message = email.message_from_string(raw_msg)
151
# see if we already have imported a message with this id for this
153
message_id = message['message-id']
154
if message_id in known_msg_ids:
155
# Skipping msg that is already imported
158
# make sure this message is in the db
161
msg = messageset.fromEmail(raw_msg, parsed_message=message,
162
create_missing_persons=True)
163
except InvalidEmailMessage:
164
self.logger.error('Invalid email: %s' % sys.exc_value)
168
# Create the link between the bug and this message.
169
malone_bug.linkMessage(msg)
171
# ok, this is a new message for this bug, so in effect something
175
# now we need to analyse the message for useful data
176
watches = bugwatchset.fromMessage(msg, malone_bug)
177
for watch in watches:
179
'New watch for #%s on %s' % (watch.bug.id, watch.url))
182
# and also for CVE ref clues
183
prior_cves = set(malone_bug.cves)
184
cveset = getUtility(ICveSet)
185
cves = cveset.inMessage(msg)
187
malone_bug.linkCVE(cve)
188
if cve not in prior_cves:
189
self.logger.info('CVE-%s (%s) found for Malone #%s' % (
190
cve.sequence, cve.status.name, malone_bug.id))
192
# now we know about this message for this bug
193
known_msg_ids.add(message_id)
195
# and best we commit, so that we can see the email that the
196
# librarian has created in the db
199
# Mark all merged bugs as duplicates of the lowest-numbered bug
200
if (len(debian_bug.mergedwith) > 0 and
201
min(debian_bug.mergedwith) > debian_bug.id):
202
for merged_id in debian_bug.mergedwith:
203
merged_bug = bugset.queryByRemoteBug(
204
debbugs_tracker, merged_id)
205
if merged_bug is not None:
206
# Bug has been imported already
207
if merged_bug.duplicateof == malone_bug:
208
# we already know about this
210
elif merged_bug.duplicateof is not None:
211
# Interesting, we think it's a dup of something else
213
'Debbugs thinks #%d is a dup of #%d' % (
214
merged_bug.id, merged_bug.duplicateof))
216
# Go ahead and merge it
218
"Malone #%d is a duplicate of Malone #%d" % (
219
merged_bug.id, malone_bug.id))
220
merged_bug.duplicateof = malone_bug.id
222
# the dup status has changed
225
# make a note of the remote watch status, if it has changed
226
if watch.remotestatus != debian_bug.status:
227
watch.remotestatus = debian_bug.status
230
# update the watch date details
231
watch.lastchecked = UTC_NOW
233
watch.lastchanged = UTC_NOW
234
self.logger.info('Watch on Malone #%d changed.' % watch.bug.id)
238
if __name__ == '__main__':
239
script = DebWatchUpdater('launchpad-debbugs-sync')
240
script.lock_and_run()