]> git.pld-linux.org Git - projects/pld-ftp-admin.git/blob - modules/ftptree.py
Fix detection of missing files on test move
[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(fsrc):
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_NODSAHEADER)
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.is_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         if not self.treename.count("archive"):
311             self.__rmolderfromsrc(test = True)
312
313         self.__rmotherfromdst(dsttree, test = True, archivetree = archivetree)
314
315         for pkg in self.marked4moving:
316             pkg.move(dsttree, test = True)
317
318     def movepkgs(self, dsttree, archivetree = None):
319         if self.do_checkbuild:
320             self.__checkbuild(self.marked4moving)
321         bailoutonerror()
322
323         self.__checkarchs(dsttree, self.marked4moving)
324         bailoutonerror()
325
326         self.__checksigns(dsttree, self.marked4moving)
327         bailoutonerror()
328
329         if not self.treename.count("archive"):
330             self.__rmolderfromsrc()
331
332         self.__rmotherfromdst(dsttree, archivetree = archivetree)
333
334         for pkg in self.marked4moving:
335             pkg.move(dsttree)
336
337     def rpmfiles(self, debugfiles = True, sourcefiles = True):
338         if self.do_checkbuild:
339             self.__checkbuild(self.marked4moving)
340
341         files = []
342         for pkg in self.marked4moving:
343             files += pkg.rpmfiles(debugfiles = debugfiles, sourcefiles = sourcefiles)
344         return files
345
346     def removepkgs(self):
347         if self.do_checkbuild:
348             self.__checkbuild(self.marked4removal)
349         bailoutonerror()
350         for pkg in self.marked4removal:
351             pkg.remove()
352
353     def mark4removal(self, wannabepkgs):
354         self.__mark4something(wannabepkgs, Pkg.mark4removal)
355
356     def mark4moving(self, wannabepkgs):
357         self.__mark4something(wannabepkgs, Pkg.mark4moving)
358
359     # Internal functions below
360     def __arch_stringify(self, list):
361         ret = []
362         dist = config.ftp_dist;
363         for arch in list:
364             ret.append(dist + '-' + arch)
365         return ' '.join(ret)
366
367     def __loadpkgnames(self):
368         def checkfiletype(name):
369             if name[-13:]=='.src.rpm.info':
370                 return True
371             else:
372                 return False
373         list = filter(checkfiletype, os.listdir(self.basedir+'/SRPMS/.metadata'))
374         self.pkgnames = map((lambda x: x[:-13]), list)
375
376     def __mark4something(self, wannabepkgs, markfunction):
377         def chopoffextension(pkg):
378             found = pkg.find('.src.rpm')
379             if found == -1:
380                 return pkg
381             else:
382                 return pkg[:found]
383
384         for wannabepkg in wannabepkgs:
385             pkgname = chopoffextension(wannabepkg)
386             if pkgname in self.pkgnames:
387                 if not pkgname in self.loadedpkgs.keys():
388                     self.loadedpkgs[pkgname]=Pkg(pkgname, self)
389                 markfunction(self.loadedpkgs[pkgname])
390             else:
391                 perror('%s not found in source tree' % pkgname)
392         bailoutonerror()
393
394     def __checkbuild(self, marked):
395         """
396         Checks queue file if all arches are built
397
398         Reads config.builderqueue to grab the info
399         """
400         f = urllib.urlopen(config.builderqueue)
401         requests = {}
402         reid = re.compile(r'^.*id=(.*) pri.*$')
403         regb = re.compile(r'^group:.*$|builders:.*$', re.M)
404         for i in re.findall(regb, f.read()):
405             if i[0] == 'g':
406                 id = reid.sub(r'\1', i)
407                 requests[id] = ""
408             elif i[0]=='b':
409                 requests[id] = requests[id] + i
410         f.close()
411
412         for pkg in marked:
413             for bid in pkg.build.keys():
414                 if requests.has_key(bid) and not requests[bid].find('?') == -1:
415                     pkg.error("(buildid %s) building not finished" % bid)
416
417     def __checkarchs(self, dsttree, marked):
418         """
419         Checks marked pkgs it is built on all archs.
420         """
421         for pkg in marked:
422             if len(pkg.files.keys()) <= 1:
423                 pkg.error('has only src.rpm built')
424                 continue
425             otherpkgnames = self.__find_other_pkgs(pkg, dsttree)
426
427             # check if we're not removing some archs
428             if otherpkgnames:
429                 curarchs = []
430                 missingarchs = []
431                 for somepkg in otherpkgnames:
432                     curarchs.extend(Pkg(somepkg, dsttree).files.keys())
433                 for arch in curarchs:
434                     if arch not in pkg.files.keys():
435                         missingarchs.append(arch)
436                 if missingarchs:
437                     pkg.error('moving would remove archs: %s' % self.__arch_stringify(missingarchs))
438             else:
439                 # warn if a package isn't built for all archs
440                 # ftp_archs + SRPMS
441                 ftp_archs_num = len(config.ftp_archs) + 1
442                 if (config.separate_noarch and 'noarch' in pkg.files.keys()):
443                     # ftp_archs + SRPMS + noarch subpackages
444                     ftp_archs_num += 1
445                     # plain simple noarch package
446                     if (len(pkg.files.keys()) == 2):
447                         continue
448
449                 if len(pkg.files.keys()) != ftp_archs_num:
450                     missingarchs = []
451                     for arch in config.ftp_archs:
452                         if arch not in pkg.files.keys():
453                             missingarchs.append(arch)
454                     pkg.warning('not built for archs: %s' % self.__arch_stringify(missingarchs))
455
456     def __checkduplicates(self, marked):
457         """
458         Checks if marked packages contain duplicate packages (with different versions)
459         """
460         for pkg in marked:
461             olderpkgnames = self.__find_older_pkgs(pkg)
462             for i in olderpkgnames:
463                 markednames = [str(x) for x in marked]
464                 if i in markednames:
465                     pkg.error('duplicate package: %s' % i)
466
467     def __rmolderfromsrc(self, test = False):
468         for pkg in self.marked4moving:
469             olderpkgnames = self.__find_older_pkgs(pkg)
470             for i in olderpkgnames:
471                 Pkg(i, self).remove(test)
472
473     def __rmotherfromdst(self, dsttree, test = False, archivetree = None):
474         for pkg in self.marked4moving:
475             pkgnames = self.__find_other_pkgs(pkg, dsttree)
476             for i in pkgnames:
477                 if archivetree == None:
478                     Pkg(i, dsttree).remove(test)
479                 else:
480                     Pkg(i, dsttree).move(archivetree, test = test)
481
482     # Used more than once filter functions
483     def __find_other_pkgs(self, pkg, tree):
484         escapedpkgname = pkg.name.replace('.', '\.').replace('+', '\+')
485         ziewre = re.compile(escapedpkgname + '-[^-]*-[^-]*$')
486         def filter_other_pkgs(x):
487             if ziewre.match(x) and not x == pkg.nvr:
488                 return True
489             else:
490                 return False
491         return filter(filter_other_pkgs, tree.pkgnames)
492
493     def __find_older_pkgs(self, pkg):
494         def filter_older_pkgs(x):
495             c = x.split('-')
496             rc = rpm.labelCompare(('0', pkg.version, pkg.release),
497                                                         ('0', c[-2], c[-1]))
498             if rc == 1: # pkg > x
499                 return True
500             else:
501                 return False
502         return filter(filter_older_pkgs, self.__find_other_pkgs(pkg, self))
503
504     def __checksigns(self, tree, pkgs, test = False):
505         """
506         Checks if pkgs in tree are all signed.
507
508         in case of test = true, error flag is set for unsigned packages
509         """
510         if not tree.treename in config.signed_trees:
511             return
512
513         for pkg in pkgs:
514             unsigned = 0
515             for file in pkg.rpmfiles():
516                 if not is_signed(file):
517                     unsigned += 1
518
519             if unsigned != 0:
520                 if test == True:
521                     if not quietmode:
522                         pkg.warning('%d files not signed' % unsigned)
523                 else:
524                     pkg.error('%d files not signed' % unsigned)
525
526     def __checkforobsoletes(self, tree, pkgs, test = False):
527         """
528         Checks queue file if package obsoletes something in destination tree and suggest for removal.
529
530         Only NAME tag is compared, i.e virtual packages do not get reported.
531
532         """
533         if test != True:
534             return
535
536         def findbyname(name):
537             def x(nvr):
538                 return '-'.join(nvr.split('-')[:-2]) == name
539             return filter(x, tree.pkgnames)
540
541         for pkg in pkgs:
542             obsoletes = pkg.obsoletes()
543             if not obsoletes:
544                 continue
545
546             for pn, setlist in obsoletes.items():
547                 for item in setlist:
548                     p = findbyname(item)
549                     if p:
550                         pkg.warning('obsoletes %s (via %s) in dest tree, perhaps you want rmpkg' % (p,pn))
551
552     def __checkforrelease(self, tree, pkgs, test = False):
553         """
554         Checks queue file if package release is non integer.
555
556         """
557         if test != True:
558             return
559
560         for pkg in pkgs:
561             if not pkg.is_release():
562                 pkg.warning('non-integer release: %s' % pkg.release)
This page took 0.088034 seconds and 3 git commands to generate.