~launchpad-pqm/launchpad/devel

10637.3.1 by Guilherme Salgado
Use the default python version instead of a hard-coded version
1
#!/usr/bin/python -S
8687.15.7 by Karl Fogel
Add the copyright header block to more files.
2
#
3
# Copyright 2009 Canonical Ltd.  This software is licensed under the
4
# GNU Affero General Public License version 3 (see the file LICENSE).
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
5
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.
8
9641.1.4 by Gary Poster
more updates for _pythonpath. Still need to update all subprocess calls to python to use -S; still need to update z3c.recipe.filetemplate to provide sys.modules from -S run.
9
import _pythonpath
2450 by Canonical.com Patch Queue Manager
[r=jamesh] rework cve structure, and general polish
10
import os
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
11
import sys
12
import email
13
import logging
14
15
# zope bits
16
from zope.component import getUtility
17
11882.2.2 by Jonathan Lange
Clear up a heck of a lot of imports from canonical.launchpad.interfaces.
18
from lp.bugs.interfaces.bug import IBugSet
19
from lp.bugs.interfaces.bugtask import (
20
    BugTaskSearchParams,
21
    IBugTaskSet,
22
    )
23
from lp.bugs.interfaces.bugwatch import IBugWatchSet
24
from lp.bugs.interfaces.cve import ICveSet
8523.3.1 by Gavin Panella
Bugs tree reorg after automated migration.
25
from lp.bugs.scripts import debbugs
8356.1.1 by Leonard Richardson
Partial move.
26
from lp.services.scripts.base import (LaunchpadCronScript,
3691.348.19 by kiko
Two more scripts converted.
27
    LaunchpadScriptFailure)
11882.2.2 by Jonathan Lange
Clear up a heck of a lot of imports from canonical.launchpad.interfaces.
28
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
29
from canonical.launchpad.interfaces.message import (
30
    InvalidEmailMessage,
31
    IMessageSet,
32
    )
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
33
from canonical.database.constants import UTC_NOW
34
35
# setup core values and defaults
2450 by Canonical.com Patch Queue Manager
[r=jamesh] rework cve structure, and general polish
36
debbugs_location_default = '/srv/bugs-mirror.debian.org/'
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
37
3691.348.19 by kiko
Two more scripts converted.
38
4264.2.1 by James Henstridge
add a LaunchpadCronScript subclass, and make cronscripts/*.py use it
39
class DebWatchUpdater(LaunchpadCronScript):
3691.348.19 by kiko
Two more scripts converted.
40
    loglevel = logging.WARNING
41
42
    def add_my_options(self):
43
        self.parser.add_option('--max', action='store', type='int', dest='max',
44
            default=None, help="The maximum number of bugs to synchronise.")
45
        self.parser.add_option('--debbugs', action='store', type='string',
46
            dest='debbugs',
47
            default=debbugs_location_default,
48
            help="The location of your debbugs database.")
49
50
    def main(self):
51
        if not os.path.exists(os.path.join(self.options.debbugs, 'index/index.db')):
52
            raise LaunchpadScriptFailure('%s is not a debbugs db.'
53
                                         % self.options.debbugs)
54
55
        self.txn.begin()
56
        self.debbugs_db = debbugs.Database(self.options.debbugs)
57
        self.sync()
58
        self.txn.commit()
59
60
        self.logger.info('Done!')
61
62
    def sync(self):
63
        changedcounter = 0
64
65
        self.logger.info('Finding existing debbugs watches...')
66
        debbugs_tracker = getUtility(ILaunchpadCelebrities).debbugs
67
        debwatches = debbugs_tracker.watches
68
69
        previousimportset = set([b.remotebug for b in debwatches])
70
        self.logger.info('%d debbugs previously imported.' % len(previousimportset))
71
72
        target_watches = [watch for watch in debwatches if watch.needscheck]
73
        self.logger.info('%d debbugs watches to syncronise.' % len(target_watches))
74
75
        self.logger.info('Sorting bug watches...')
76
        target_watches.sort(key=lambda a: a.remotebug)
77
78
        self.logger.info('Syncing bug watches...')
79
        for watch in target_watches:
80
            if self.sync_watch(watch):
81
                changedcounter += 1
82
                self.txn.commit()
83
            if self.options.max:
84
                if changedcounter >= self.options.max:
85
                    self.logger.info('Synchronised %d bugs!' % changedcounter)
86
                    return
87
88
    def sync_watch(self, watch):
89
        # keep track of whether or not something changed
90
        waschanged = False
91
        # find the bug in malone
92
        malone_bug = watch.bug
93
        # find the bug in debbugs
94
        debian_bug = self.debbugs_db[int(watch.remotebug)]
95
        bugset = getUtility(IBugSet)
96
        bugtaskset = getUtility(IBugTaskSet)
97
        bugwatchset = getUtility(IBugWatchSet)
98
        messageset = getUtility(IMessageSet)
99
        debian = getUtility(ILaunchpadCelebrities).debian
100
        debbugs_tracker = getUtility(ILaunchpadCelebrities).debbugs
101
102
        # make sure we have tasks for all the debian package linkages, and also
103
        # make sure we have updated their status and severity appropriately
104
        for packagename in debian_bug.packagelist():
105
            try:
106
                srcpkgname, binpkgname = debian.guessPackageNames(packagename)
107
            except ValueError:
108
                self.logger.error(sys.exc_value)
109
                continue
110
            search_params = BugTaskSearchParams(user=None, bug=malone_bug,
111
                sourcepackagename=srcpkgname)
112
            search_params.setDistribution(debian)
113
            bugtasks = bugtaskset.search(search_params)
114
            if len(bugtasks) == 0:
115
                # we need a new task to link the bug to the debian package
116
                self.logger.info('Linking %d and debian %s/%s' % (
117
                    malone_bug.id, srcpkgname.name, binpkgname.name))
4664.1.1 by Curtis Hovey
Normalized comments for bug 3732.
118
                # XXX: kiko 2007-02-03:
119
                # This code is completely untested and broken.
3691.348.19 by kiko
Two more scripts converted.
120
                bugtask = malone_bug.addTask(
121
                    owner=malone_bug.owner, distribution=debian,
122
                    sourcepackagename=srcpkgname)
123
                bugtask.bugwatch = watch
124
                waschanged = True
125
            else:
126
                assert len(bugtasks) == 1, 'Should only find a single task'
127
                bugtask = bugtasks[0]
128
            status = bugtask.status
129
            if status <> bugtask.setStatusFromDebbugs(debian_bug.status):
130
                waschanged = True
131
            severity = bugtask.severity
132
            if severity <> bugtask.setSeverityFromDebbugs(debian_bug.severity):
133
                waschanged = True
134
135
        known_msg_ids = set([msg.rfc822msgid for msg in malone_bug.messages])
136
137
        for raw_msg in debian_bug.comments:
138
139
            # parse it so we can extract the message id easily
140
            message = email.message_from_string(raw_msg)
141
142
            # see if we already have imported a message with this id for this
143
            # bug
144
            message_id = message['message-id']
145
            if message_id in known_msg_ids:
146
                # Skipping msg that is already imported
147
                continue
148
149
            # make sure this message is in the db
150
            msg = None
151
            try:
152
                msg = messageset.fromEmail(raw_msg, parsed_message=message,
153
                    create_missing_persons=True)
154
            except InvalidEmailMessage:
155
                self.logger.error('Invalid email: %s' % sys.exc_value)
156
            if msg is None:
157
                continue
158
159
            # create the link between the bug and this message
160
            bugmsg = malone_bug.linkMessage(msg)
161
162
            # ok, this is a new message for this bug, so in effect something has
163
            # changed
164
            waschanged = True
165
166
            # now we need to analyse the message for useful data
167
            watches = bugwatchset.fromMessage(msg, malone_bug)
168
            for watch in watches:
169
                self.logger.info('New watch for #%s on %s' % (watch.bug.id, watch.url))
170
                waschanged = True
171
172
            # and also for CVE ref clues
173
            prior_cves = set(malone_bug.cves)
174
            cveset = getUtility(ICveSet)
175
            cves = cveset.inMessage(msg)
176
            for cve in cves:
177
                malone_bug.linkCVE(cve)
178
                if cve not in prior_cves:
179
                    self.logger.info('CVE-%s (%s) found for Malone #%s' % (
180
                        cve.sequence, cve.status.name, malone_bug.id))
181
182
            # now we know about this message for this bug
183
            known_msg_ids.add(message_id)
184
185
            # and best we commit, so that we can see the email that the
186
            # librarian has created in the db
187
            self.txn.commit()
188
189
        # Mark all merged bugs as duplicates of the lowest-numbered bug
190
        if (len(debian_bug.mergedwith) > 0 and
191
            min(debian_bug.mergedwith) > debian_bug.id):
192
            for merged_id in debian_bug.mergedwith:
193
                merged_bug = bugset.queryByRemoteBug(debbugs_tracker, merged_id)
194
                if merged_bug is not None:
195
                    # Bug has been imported already
196
                    if merged_bug.duplicateof == malone_bug:
197
                        # we already know about this
198
                        continue
199
                    elif merged_bug.duplicateof is not None:
200
                        # Interesting, we think it's a dup of something else
201
                        self.logger.warning('Debbugs thinks #%d is a dup of #%d' % (
202
                            merged_bug.id, merged_bug.duplicateof))
203
                        continue
204
                    # Go ahead and merge it
205
                    self.logger.info("Malone #%d is a duplicate of Malone #%d" % (
206
                        merged_bug.id, malone_bug.id))
207
                    merged_bug.duplicateof = malone_bug.id
208
209
                    # the dup status has changed
210
                    waschanged = True
211
212
        # make a note of the remote watch status, if it has changed
213
        if watch.remotestatus <> debian_bug.status:
214
            watch.remotestatus = debian_bug.status
215
            waschanged = True
216
217
        # update the watch date details
218
        watch.lastchecked = UTC_NOW
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
219
        if waschanged:
3691.348.19 by kiko
Two more scripts converted.
220
            watch.lastchanged = UTC_NOW
221
            self.logger.info('Watch on Malone #%d changed.' % watch.bug.id)
222
        return waschanged
223
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
224
225
if __name__ == '__main__':
3691.348.19 by kiko
Two more scripts converted.
226
    script = DebWatchUpdater('launchpad-debbugs-sync')
227
    script.lock_and_run()
2048 by Canonical.com Patch Queue Manager
debbugssync, hct enabling, and ui fixes. r=jamesh
228