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