]> git.pld-linux.org Git - projects/pld-ftp-admin.git/blob - modules/ftptree.py
- allow filtering source and debugfiles in rpmfiles() method
[projects/pld-ftp-admin.git] / modules / ftptree.py
1 # vi: encoding=utf-8 ts=8 sts=4 sw=4 et
2
3 import os, config, string, urllib, re, rpm
4 from common import fileexists, noarchcachedir
5 from baseftptree import BasePkg, BaseFtpTree
6 from sign import is_signed
7
8 errnum = 0
9 quietmode = False
10
11 class SomeError(Exception):
12     def __init__(self):
13         return
14
15     def __str__(self):
16         print "","An Error occured!"
17
18 def bailoutonerror():
19     if not errnum == 0:
20         print "%d error(s) encountered... aborting" % errnum
21         raise SomeError
22
23 def pinfo(msg):
24     print 'INFO: ' + msg
25
26 def perror(msg):
27     global errnum
28     errnum = errnum + 1
29     print 'ERR: ' + msg
30
31 def pwarning(msg):
32     print 'WARN: ' + msg
33
34 def rm(file, test = False):
35     if test:
36         if not os.path.exists(file):
37             pinfo("TEST os.remove(%s): file doesn't exists" % file)
38     else:
39         try:
40             os.remove(file)
41         except OSError, e:
42             pinfo("os.remove(%s): %s" % (file, e))
43             #raise
44
45 def mv(src, dst, test = False):
46     fsrc = src
47     fdst = dst + '/' + src.split('/')[-1]
48     if test:
49         if not os.path.exists(src):
50             pinfo("TEST os.rename(%s, %s): source doesn't exists" % (fsrc, fdst))
51         if not os.path.exists(dst):
52             pinfo("TEST destination doesn't exist: %s" % dst)
53     else:
54         try:
55             os.rename(fsrc, fdst)
56         except OSError, e:
57             pinfo("os.rename(%s, %s): %s" % (fsrc, fdst, e))
58             raise
59
60 class Pkg(BasePkg):
61     def __init__(self, nvr, tree):
62         BasePkg.__init__(self, nvr, tree)
63         self.name = string.join(nvr.split('-')[:-2], '-')
64         self.version = nvr.split('-')[-2]
65         self.release = nvr.split('-')[-1]
66         self.marked4removal = False
67         self.marked4moving = False
68         self.marked4movingpool = []
69         self.errors = []
70         self.warnings = []
71
72     def __cmp__(self, pkg):
73         if self.name > pkg.name:
74             return 1
75         elif self.name < pkg.name:
76             return -1
77         else:
78             return rpm.labelCompare(('0', self.version, self.release),
79                                     ('0', pkg.version, pkg.release))
80
81
82     # unfortunately can't do new Pkg(NVR), and have no "tree" in this pkg context
83     # so this static function
84     def is_debuginfo(self, nvr):
85         """
86         returns true if NVR is debuginfo package and separate debuginfo is enabled
87         """
88         if not config.separate_debuginfo:
89             return False
90         pkg = nvr.split('-')[:-2]
91         return pkg[-1] == 'debuginfo'
92
93     def is_sourcefile(self, file):
94         """
95         returns true if file is source package
96         """
97         return file[-8:] == '.src.rpm'
98
99     # returns true if package build is integer
100     def is_release(self):
101         """
102         To account Release tags with subver macros, we consider integer release
103         if it contains odd number of dots:
104
105         1 -> True
106         0.1 -> False
107         0.%{subver}.%{rel}, %{rel} = 1 -> 0.20010.1 -> True
108         0.%{subver}.%{rel}, %{rel} = 0.1 -> 0.20010.0.1 -> False
109         """
110         return self.release.count('.') % 2 == 0
111
112     def mark4moving(self):
113         if not self.marked4moving:
114             # Only one pkg in this pool can be marked for moving
115             for pkg in self.marked4movingpool:
116                 pkg.unmark4moving()
117             self.tree.marked4moving.append(self)
118             self.marked4moving=True
119
120     def unmark4moving(self):
121         if self.marked4moving:
122             self.tree.marked4moving.remove(self)
123             self.marked4moving=False
124
125     def mark4removal(self):
126         if not self.marked4removal:
127             self.tree.marked4removal.append(self)
128             self.marked4removal=True
129
130     def error(self, msg):
131         self.errors.append(msg)
132         if not quietmode:
133             perror('%s %s' % (self.nvr, msg))
134
135     def warning(self, msg):
136         self.warnings.append(msg)
137         if not quietmode:
138             pwarning('%s %s' % (self.nvr, msg))
139
140     def load(self, content=None):
141         BasePkg.load(self, content)
142         if self.info.has_key('move'):
143             self.mark4moving()
144
145     def writeinfo(self):
146         f = open(self.tree.basedir+'/SRPMS/.metadata/'+self.nvr+'.src.rpm.info', 'w')
147         for bid in self.build.keys():
148             f.write("info:build:%s:requester:%s\ninfo:build:%s:requester_email:%s\n" % (bid, self.build[bid].requester, bid, self.build[bid].requester_email))
149         for key in self.info.keys():
150             f.write("info:%s:%s\n" % (key, string.join(self.info[key], ':')))
151         for arch in self.files.keys():
152             for rpm in self.files[arch]:
153                 f.write("file:%s:%s\n" % (arch, rpm))
154         
155     def remove(self, test = False):
156         """
157         Remove package from ftp
158         """
159         for arch in self.files.keys():
160             for rpm in self.files[arch]:
161                 if self.is_debuginfo(rpm):
162                     rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
163                 else:
164                     rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
165                 if arch == 'noarch':
166                     if fileexists(noarchcachedir + rpm + '.filelist'):
167                         rm(noarchcachedir + rpm + '.filelist', test)
168                     if fileexists(noarchcachedir + rpm + '.reqlist'):
169                         rm(noarchcachedir + rpm + '.reqlist', test)
170         rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
171
172     def rpmfiles(self, debugfiles = True, sourcefiles  = True):
173         """
174         Return rpm files related to this package
175         """
176         files = []
177         for arch, rpms in self.files.items():
178             for nvr in rpms:
179                 if self.is_debuginfo(nvr):
180                     if debugfiles:
181                         files.append(self.tree.basedir + '/' + arch + '/debuginfo/' + nvr)
182                 else:
183                     if self.is_sourcefile(nvr):
184                         if sourcefiles:
185                             files.append(self.tree.basedir + '/' + arch + '/RPMS/' + nvr)
186                     else:
187                         files.append(self.tree.basedir + '/' + arch + '/RPMS/' + nvr)
188         return files
189
190     def obsoletes(self):
191         """
192         Return obsoletes for all packages in Pkg:
193
194         {'php-geshi': set(['geshi'])}
195
196         """
197         def rpmhdr(pkg):
198             ts = rpm.ts()
199             ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
200             fdno = os.open(pkg, os.O_RDONLY)
201             hdr = ts.hdrFromFdno(fdno)
202             os.close(fdno)
203             return hdr
204
205         obsoletes = {}
206         for rpmfile in self.rpmfiles():
207             if not os.path.exists(rpmfile):
208                 continue
209             hdr = rpmhdr(rpmfile)
210             if not hdr[rpm.RPMTAG_OBSOLETES]:
211                 continue
212
213             name = hdr[rpm.RPMTAG_NAME]
214             if not name in obsoletes:
215                 obsoletes[name] = set()
216
217             for tag in hdr[rpm.RPMTAG_OBSOLETES]:
218                 obsoletes[name].add(tag)
219
220         return obsoletes
221
222     def move(self, dsttree, test = False):
223         if dsttree.has_key(self.nvr):
224             movedany = False
225             for arch in self.files.keys():
226                 if arch in dsttree[self.nvr].files.keys():
227                     msg = ""
228                     if test:
229                         msg = "TEST "
230                     pinfo("%sArch %s for %s is already present in dest tree; removing from srctree" % (msg, arch, self.nvr))
231                     for rpm in self.files[arch]:
232                         if self.il-page-warningus_debuginfo(rpm):
233                             rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
234                         else:
235                             rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
236                 else:
237                     movedany = True
238                     dsttree[self.nvr].files[arch] = self.files[arch]
239                     for rpm in self.files[arch]:
240                         if self.is_debuginfo(rpm):
241                             mv(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, dsttree.basedir + '/' + arch + '/debuginfo/', test)
242                         else:
243                             mv(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, dsttree.basedir + '/' + arch + '/RPMS/', test)
244             if not test and movedany:
245                 for bid in self.build.keys():
246                     dsttree[self.nvr].build[bid] = self.build[bid]
247                 dsttree[self.nvr].writeinfo()
248             rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
249         else:
250             # move files
251             for arch in self.files.keys():
252                 for rpm in self.files[arch]:
253                     if self.is_debuginfo(rpm):
254                         mv(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, dsttree.basedir + '/' + arch + '/debuginfo/', test)
255                     else:
256                         mv(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, dsttree.basedir + '/' + arch + '/RPMS/', test)
257
258             # move metadata
259             mv(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', dsttree.basedir + '/SRPMS/.metadata/', test)
260
261 class FtpTree(BaseFtpTree):
262     def __init__(self, tree, loadall=False):
263         BaseFtpTree.__init__(self, tree)
264         self.loadedpkgs = {}
265         self.marked4removal = []
266         self.marked4moving = []
267         self.pkgnames = []
268         self.__loadpkgnames()
269         if loadall:
270             for pkgname in self.pkgnames:
271                 self.loadedpkgs[pkgname] = Pkg(pkgname, self)
272         # Tests:
273         self.do_checkbuild = True
274
275     def __getitem__(self, key):
276         if self.loadedpkgs.has_key(key):
277             return self.loadedpkgs[key]
278         elif key in self.pkgnames:
279             pkg=Pkg(key, self)
280             self.loadedpkgs[key]=pkg
281             return pkg
282         else:
283             raise KeyError, key
284
285     def has_key(self, key):
286         if key in self.pkgnames:
287             return True
288         else:
289             return False
290
291     def keys(self):
292         return self.pkgnames
293
294     def values(self):
295         return self.loadedpkgs.values()
296
297     def checktree(self, dsttree):
298         self.__checkbuild(self.loadedpkgs.values())
299         self.__checkarchs(dsttree, self.loadedpkgs.values())
300
301     def testmove(self, dsttree, archivetree = None):
302         self.__checkbuild(self.marked4moving)
303         self.__checkarchs(dsttree, self.marked4moving)
304         self.__checkduplicates(self.marked4moving)
305
306         self.__checksigns(dsttree, self.marked4moving, test = True)
307         self.__checkforobsoletes(dsttree, self.marked4moving, test = True)
308         self.__checkforrelease(dsttree, self.marked4moving, test = True)
309         
310         self.__rmolderfromsrc(test = True)
311         self.__rmotherfromdst(dsttree, test = True, archivetree = archivetree)
312
313         for pkg in self.marked4moving:
314             pkg.move(dsttree, test = True)
315
316     def movepkgs(self, dsttree, archivetree = None):
317         if self.do_checkbuild:
318             self.__checkbuild(self.marked4moving)
319         bailoutonerror()
320
321         self.__checkarchs(dsttree, self.marked4moving)
322         bailoutonerror()
323
324         self.__checksigns(dsttree, self.marked4moving)
325         bailoutonerror()
326
327         self.__rmolderfromsrc()
328         self.__rmotherfromdst(dsttree, archivetree = archivetree)
329
330         for pkg in self.marked4moving:
331             pkg.move(dsttree)
332
333     def rpmfiles(self, debugfiles = True, sourcefiles = True):
334         if self.do_checkbuild:
335             self.__checkbuild(self.marked4moving)
336
337         files = []
338         for pkg in self.marked4moving:
339             files += pkg.rpmfiles(debugfiles = debugfiles, sourcefiles = sourcefiles)
340         return files
341
342     def removepkgs(self):
343         if self.do_checkbuild:
344             self.__checkbuild(self.marked4removal)
345         bailoutonerror()
346         for pkg in self.marked4removal:
347             pkg.remove()
348
349     def mark4removal(self, wannabepkgs):
350         self.__mark4something(wannabepkgs, Pkg.mark4removal)
351
352     def mark4moving(self, wannabepkgs):
353         self.__mark4something(wannabepkgs, Pkg.mark4moving)
354         
355
356     # Internal functions below
357     def __arch_stringify(self, list):
358         ret = []
359         # XXX: is dist line in any config?
360         dist = 'ac'
361         for arch in list:
362             ret.append(dist + '-' + arch)
363         return ' '.join(ret)
364
365     def __loadpkgnames(self):
366         def checkfiletype(name):
367             if name[-13:]=='.src.rpm.info':
368                 return True
369             else:
370                 return False
371         list = filter(checkfiletype, os.listdir(self.basedir+'/SRPMS/.metadata'))
372         self.pkgnames = map((lambda x: x[:-13]), list)
373
374     def __mark4something(self, wannabepkgs, markfunction):
375         def chopoffextension(pkg):
376             found = pkg.find('.src.rpm')
377             if found == -1:
378                 return pkg
379             else:
380                 return pkg[:found]
381
382         for wannabepkg in wannabepkgs:
383             pkgname = chopoffextension(wannabepkg)
384             if pkgname in self.pkgnames:
385                 if not pkgname in self.loadedpkgs.keys():
386                     self.loadedpkgs[pkgname]=Pkg(pkgname, self)
387                 markfunction(self.loadedpkgs[pkgname])
388             else:
389                 perror('%s not found in source tree' % pkgname)
390         bailoutonerror()
391
392     def __checkbuild(self, marked):
393         """
394         Checks queue file if all arches are built
395
396         Reads config.builderqueue to grab the info
397         """
398         f = urllib.urlopen(config.builderqueue)
399         requests = {}
400         reid = re.compile(r'^.*id=(.*) pri.*$')
401         regb = re.compile(r'^group:.*$|builders:.*$', re.M)
402         for i in re.findall(regb, f.read()):
403             if i[0] == 'g':
404                 id = reid.sub(r'\1', i)
405                 requests[id] = ""
406             elif i[0]=='b':
407                 requests[id] = requests[id] + i
408         f.close()
409
410         for pkg in marked:
411             for bid in pkg.build.keys():
412                 if requests.has_key(bid) and not requests[bid].find('?') == -1:
413                     pkg.error("(buildid %s) building not finished" % bid)
414
415     def __checkarchs(self, dsttree, marked):
416         """
417         Checks marked pkgs it is built on all archs.
418         """
419         for pkg in marked:
420             if len(pkg.files.keys()) <= 1:
421                 pkg.error('has only src.rpm built')
422                 continue
423             otherpkgnames = self.__find_other_pkgs(pkg, dsttree)
424
425             # check if we're not removing some archs
426             if otherpkgnames:
427                 curarchs = []
428                 missingarchs = []
429                 for somepkg in otherpkgnames:
430                     curarchs.extend(Pkg(somepkg, dsttree).files.keys())
431                 for arch in curarchs:
432                     if arch not in pkg.files.keys():
433                         missingarchs.append(arch)
434                 if missingarchs:
435                     pkg.error('moving would remove archs: %s' % self.__arch_stringify(missingarchs))
436             else:
437                 # warn if a package isn't built for all archs
438                 if (config.separate_noarch and 'noarch' in pkg.files.keys() and len(pkg.files.keys()) == 2):
439                     continue
440                 elif len(pkg.files.keys()) != len(config.ftp_archs) + 1:
441                     missingarchs = []
442                     for arch in config.ftp_archs:
443                         if arch not in pkg.files.keys():
444                             missingarchs.append(arch)
445                     pkg.warning('not built for archs: %s' % self.__arch_stringify(missingarchs))
446
447     def __checkduplicates(self, marked):
448         """
449         Checks if marked packages contain duplicate packages (with different versions)
450         """
451         for pkg in marked:
452             olderpkgnames = self.__find_older_pkgs(pkg)
453             for i in olderpkgnames:
454                 pkg.error('duplicate package: %s' % i)
455
456     def __rmolderfromsrc(self, test = False):
457         for pkg in self.marked4moving:
458             olderpkgnames = self.__find_older_pkgs(pkg)
459             for i in olderpkgnames:
460                 Pkg(i, self).remove(test)
461
462     def __rmotherfromdst(self, dsttree, test = False, archivetree = None):
463         for pkg in self.marked4moving:
464             pkgnames = self.__find_other_pkgs(pkg, dsttree)
465             for i in pkgnames:
466                 if archivetree == None:
467                     Pkg(i, dsttree).remove(test)
468                 else:
469                     Pkg(i, dsttree).move(archivetree, test = test)
470
471     # Used more than once filter functions
472     def __find_other_pkgs(self, pkg, tree):
473         escapedpkgname = pkg.name.replace('.', '\.').replace('+', '\+')
474         ziewre = re.compile(escapedpkgname + '-[^-]*-[^-]*$')
475         def filter_other_pkgs(x):
476             if ziewre.match(x) and not x == pkg.nvr:
477                 return True
478             else:
479                 return False
480         return filter(filter_other_pkgs, tree.pkgnames)
481
482     def __find_older_pkgs(self, pkg):
483         def filter_older_pkgs(x):
484             c = x.split('-')
485             rc = rpm.labelCompare(('0', pkg.version, pkg.release),
486                                                         ('0', c[-2], c[-1]))
487             if rc == 1: # pkg > x
488                 return True
489             else:
490                 return False
491         return filter(filter_older_pkgs, self.__find_other_pkgs(pkg, self))
492
493     def __checksigns(self, tree, pkgs, test = False):
494         """
495         Checks if pkgs in tree are all signed.
496
497         in case of test = true, error flag is set for unsigned packages
498         """
499         if not tree.treename in config.signed_trees:
500             return
501
502         for pkg in pkgs:
503             unsigned = 0
504             for file in pkg.rpmfiles():
505                 if not is_signed(file):
506                     unsigned += 1
507
508             if unsigned != 0:
509                 if test == True:
510                     if not quietmode:
511                         pkg.warning('%d files not signed' % unsigned)
512                 else:
513                     pkg.error('%d files not signed' % unsigned)
514
515     def __checkforobsoletes(self, tree, pkgs, test = False):
516         """
517         Checks queue file if package obsoletes something in destination tree and suggest for removal.
518
519         Only NAME tag is compared, i.e virtual packages do not get reported.
520
521         """
522         if test != True:
523             return
524
525         def findbyname(name):
526             def x(nvr):
527                 return '-'.join(nvr.split('-')[:-2]) == name
528             return filter(x, tree.pkgnames)
529
530         for pkg in pkgs:
531             obsoletes = pkg.obsoletes()
532             if not obsoletes:
533                 continue
534
535             for pn, setlist in obsoletes.items():
536                 for item in setlist:
537                     p = findbyname(item)
538                     if p:
539                         pkg.warning('obsoletes %s (via %s) in dest tree, perhaps you want rmpkg' % (p,pn))
540
541     def __checkforrelease(self, tree, pkgs, test = False):
542         """
543         Checks queue file if package release is non integer.
544
545         """
546         if test != True:
547             return
548
549         for pkg in pkgs:
550             if not pkg.is_release():
551                 pkg.warning('non-integer release: %s' % pkg.release)
This page took 0.085357 seconds and 3 git commands to generate.