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