summaryrefslogtreecommitdiffstats
path: root/PackageOwners.py
blob: e79f1670fa56b30117bb9026630132f37aae27c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/usr/bin/python
# -*- mode: Python; indent-tabs-mode: nil; -*-
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import commands
import errno
import os, sys, time
import shutil
import tempfile
from urllib import FancyURLopener


class AccountsURLopener(FancyURLopener):
    """Subclass of urllib.FancyURLopener to allow passing http basic auth info"""
    def __init__(self, username, password):
        FancyURLopener.__init__(self)
        self.username = username
        self.password = password

    def prompt_user_passwd(self, host, realm):
        return (self.username, self.password)


class PackageOwners:
    """interface to Fedora package owners list (and Fedora Extras owners/owners.list file)"""

    def __init__(self):
        self.dict = {}
        self.how = 'unknown'


    def FromURL(self, retries=3, retrysecs=300, url='https://admin.fedoraproject.org/pkgdb/acls/bugzilla?tg_format=plain',
                pkgdb=True, username=None, password=None):
        # old url='http://cvs.fedora.redhat.com/viewcvs/*checkout*/owners/owners.list?root=extras'
        if pkgdb:
            self.how = 'pkgdb'
        else:
            self.how = 'url'
        self.url = url
        self.retries = retries
        self.retrysecs = retrysecs
        self.username = username
        self.password = password
        return self._refresh()


    def FromCVS(self, retries=3, retrysecs=300, command='LC_ALL=C CVS_RSH=ssh cvs -f -d :pserver:anonymous@cvs.fedora.redhat.com:/cvs/extras co owners', workdir=''):
        self.how = 'cvs'
        self.command = command
        self.retries = retries
        self.retrysecs = retrysecs
        self.workdir = workdir
        self.ownersfile = os.path.join('owners', 'owners.list')
        self.cwdstack = []
        return self._refresh()


    def __getitem__(self,rpmname):
        """return e-mail address from initialowner field"""
        return self.GetOwner(rpmname)


    def GetOwner(self,rpmname):
        """return e-mail address from initialowner field"""
        try:
            r = self.dict[rpmname]['mailto']
        except KeyError:
            r = ''
        return r


    def GetOwners(self,rpmname):
        """return list of e-mail addresses from initialowner+initialcclist fields"""
        r = self.GetCoOwnerList(rpmname)
        r2 = self.GetOwner(rpmname)
        if len(r2):
            r.append(r2)
        return r


    def GetCoOwnerList(self,rpmname):
        """return list of e-mail addresses from initialcclist field"""
        try:
            r = self.dict[rpmname]['cc']
        except KeyError:
            r = []
        return r


    def _enterworkdir(self):
        self.cwdstack.append( os.getcwd() )
        if self.workdir != '':
            os.chdir(self.workdir)


    def _leaveworkdir(self):
        if len(self.cwdstack):
            os.chdir( self.cwdstack.pop() )


    def _refresh(self):
        self.dict = {}  # map package name to email address, dict[name]
        return self._download()


    def _parse(self,ownerslist):
        for line in ownerslist:
            if line.startswith('#') or line.isspace():
                continue
            try:
                (repo,pkgname,summary,emails,qacontact,cc) = line.rstrip().split('|')
                # The PkgDb includes repo's other than Fedora (Fedora EPEL,
                # Fedora OLPC, and Red Hat Linux, for example).  Skip them.
                if repo != 'Fedora':
                    continue
                def fixaddr(a):
                    # Old Fedora CVS owners.list contains e-mail addresses.
                    # PkgDb plain output contains usernames only.
                    if not self.how == 'pkgdb':
                        return a
                    if not self.usermap.has_key(a):
                        return a
                    return self.usermap[a]

                addrs = []
                mailto = '' # primary pkg owner
                if len(emails):
                    if emails.find(',')>=0:
                        (addrs) = emails.split(',')
                        mailto = addrs[0]
                        addrs = addrs[1:]
                    else:
                        mailto = emails
                    mailto = fixaddr(mailto)

                ccaddrs = []
                if len(cc):
                    (ccaddrs) = cc.split(',')
                addrs += ccaddrs
                addrs = map(lambda a: fixaddr(a), addrs)

                self.dict[pkgname] = {
                    'mailto' : mailto,
                    'cc' : addrs
                    }
            except:
                print 'ERROR: owners.list is broken'
                print line


    def _downloadfromcvs(self):
        self._enterworkdir()
        # Dumb caching. Check that file exists and is "quite recent".
        cached = False
        try:
            fstats = os.stat(self.ownersfile)
            if ( fstats.st_size and
                 ((time.time() - fstats.st_ctime) < 3600*2) ):
                cached = True
        except OSError:
            pass

        if not cached:
            # Remove 'owners' directory contents, if it exists.
            for root, dirs, files in os.walk( 'owners', topdown=False ):
                for fname in files:
                    os.remove(os.path.join( root, fname ))
                for dname in dirs:
                    os.rmdir(os.path.join( root, dname ))
            # Retry CVS checkout a few times.
            for count in range(self.retries):
                (rc, rv) = commands.getstatusoutput(self.command)
                if not rc:
                    break
                print rv
                time.sleep(self.retrysecs)
            if rc:
                # TODO: customise behaviour on error conditions
                self._leaveworkdir()
                return False

        try:
            f = file( self.ownersfile )
        except IOError, (err, strerr):
            print 'ERROR: %s' % strerr
            # TODO: customise behaviour on error conditions
            self._leaveworkdir()
            return err
        ownerslist = f.readlines()
        f.close()
        self._parse(ownerslist)
        self._leaveworkdir()
        return True


    def _getlinesfromurl(self,url):
        err = 0
        strerr = ''
        # Retry URL download a few times.
        for count in range(self.retries):
            if count != 0:
                time.sleep(self.retrysecs)
            try:
                opener = AccountsURLopener(self.username, self.password)
                f = opener.open(url)
                rc = 0
                if 'www-authenticate' in f.headers:
                    rc = 1
                    strerr = 'Authentication is required to access %s' % url
                break
            except IOError, (_err, _strerr):
                rc = 1
                print url
                print _strerr
                (err,strerr) = (_err,_strerr)
        if rc:
            raise IOError, (err, strerr)
        else:
            l = f.readlines()
            f.close()
            return l


    def _downloadfromurl(self):
        self._parse(self._getlinesfromurl(self.url))
        return True


    def _downloadfrompkgdb(self):
        fasdump = self._getlinesfromurl('https://admin.fedoraproject.org/accounts/dump-group.cgi')
        self.usermap = {}
        for line in fasdump:
            fields = line.split(',')
            user = fields[0]
            addr = fields[1]
            if (addr.find('@') < 0):  # unexpected, no addr
                print 'No email in:', line
                raise Exception
            self.usermap[user] = addr
        self._parse(self._getlinesfromurl(self.url))
        return True


    def _download(self):
        if self.how == 'url':
            return self._downloadfromurl()
        elif self.how == 'pkgdb':
            return self._downloadfrompkgdb()
        elif self.how == 'cvs':
            return self._downloadfromcvs()
        else:
            self.__init__()
            return False