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