]> git.pld-linux.org Git - projects/pld-ftp-admin.git/blob - modules/ftptree.py
b3a09da152bc2e02bb39b548dcd40874697a2b47
[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             fdno = os.open(pkg, os.O_RDONLY)
200             hdr = ts.hdrFromFdno(fdno)
201             os.close(fdno)
202             return hdr
203
204         obsoletes = {}
205         for rpmfile in self.rpmfiles():
206             if not os.path.exists(rpmfile):
207                 continue
208             hdr = rpmhdr(rpmfile)
209             if not hdr[rpm.RPMTAG_OBSOLETES]:
210                 continue
211
212             name = hdr[rpm.RPMTAG_NAME]
213             if not name in obsoletes:
214                 obsoletes[name] = set()
215
216             for tag in hdr[rpm.RPMTAG_OBSOLETES]:
217                 obsoletes[name].add(tag)
218
219         return obsoletes
220
221     def move(self, dsttree, test = False):
222         if dsttree.has_key(self.nvr):
223             movedany = False
224             for arch in self.files.keys():
225                 if arch in dsttree[self.nvr].files.keys():
226                     msg = ""
227                     if test:
228                         msg = "TEST "
229                     pinfo("%sArch %s for %s is already present in dest tree; removing from srctree" % (msg, arch, self.nvr))
230                     for rpm in self.files[arch]:
231                         if self.is_debuginfo(rpm):
232                             rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
233                         else:
234                             rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
235                 else:
236                     movedany = True
237                     dsttree[self.nvr].files[arch] = self.files[arch]
238                     for rpm in self.files[arch]:
239                         if self.is_debuginfo(rpm):
240                             mv(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, dsttree.basedir + '/' + arch + '/debuginfo/', test)
241                         else:
242                             mv(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, dsttree.basedir + '/' + arch + '/RPMS/', test)
243             if not test and movedany:
244                 for bid in self.build.keys():
245                     dsttree[self.nvr].build[bid] = self.build[bid]
246                 dsttree[self.nvr].writeinfo()
247             rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
248         else:
249             # move files
250             for arch in self.files.keys():
251                 for rpm in self.files[arch]:
252                     if self.is_debuginfo(rpm):
253                         mv(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, dsttree.basedir + '/' + arch + '/debuginfo/', test)
254                     else:
255                         mv(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, dsttree.basedir + '/' + arch + '/RPMS/', test)
256
257             # move metadata
258             mv(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', dsttree.basedir + '/SRPMS/.metadata/', test)
259
260 class FtpTree(BaseFtpTree):
261     def __init__(self, tree, loadall=False):
262         BaseFtpTree.__init__(self, tree)
263         self.loadedpkgs = {}
264         self.marked4removal = []
265         self.marked4moving = []
266         self.pkgnames = []
267         self.__loadpkgnames()
268         if loadall:
269             for pkgname in self.pkgnames:
270                 self.loadedpkgs[pkgname] = Pkg(pkgname, self)
271         # Tests:
272         self.do_checkbuild = True
273
274     def __getitem__(self, key):
275         if self.loadedpkgs.has_key(key):
276             return self.loadedpkgs[key]
277         elif key in self.pkgnames:
278             pkg=Pkg(key, self)
279             self.loadedpkgs[key]=pkg
280             return pkg
281         else:
282             raise KeyError, key
283
284     def has_key(self, key):
285         if key in self.pkgnames:
286             return True
287         else:
288             return False
289
290     def keys(self):
291         return self.pkgnames
292
293     def values(self):
294         return self.loadedpkgs.values()
295
296     def checktree(self, dsttree):
297         self.__checkbuild(self.loadedpkgs.values())
298         self.__checkarchs(dsttree, self.loadedpkgs.values())
299
300     def testmove(self, dsttree, archivetree = None):
301         self.__checkbuild(self.marked4moving)
302         self.__checkarchs(dsttree, self.marked4moving)
303         self.__checkduplicates(self.marked4moving)
304
305         self.__checksigns(dsttree, self.marked4moving, test = True)
306         self.__checkforobsoletes(dsttree, self.marked4moving, test = True)
307         self.__checkforrelease(dsttree, self.marked4moving, test = True)
308
309         if not self.treename.count("archive"):
310             self.__rmolderfromsrc(test = True)
311
312         self.__rmotherfromdst(dsttree, test = True, archivetree = archivetree)
313
314         for pkg in self.marked4moving:
315             pkg.move(dsttree, test = True)
316
317     def movepkgs(self, dsttree, archivetree = None):
318         if self.do_checkbuild:
319             self.__checkbuild(self.marked4moving)
320         bailoutonerror()
321
322         self.__checkarchs(dsttree, self.marked4moving)
323         bailoutonerror()
324
325         self.__checksigns(dsttree, self.marked4moving)
326         bailoutonerror()
327
328         if not self.treename.count("archive"):
329             self.__rmolderfromsrc()
330
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                 if (config.separate_noarch and 'noarch' in pkg.files.keys() and len(pkg.files.keys()) == 2):
440                     continue
441                 elif len(pkg.files.keys()) != len(config.ftp_archs) + 1:
442                     missingarchs = []
443                     for arch in config.ftp_archs:
444                         if arch not in pkg.files.keys():
445                             missingarchs.append(arch)
446                     pkg.warning('not built for archs: %s' % self.__arch_stringify(missingarchs))
447
448     def __checkduplicates(self, marked):
449         """
450         Checks if marked packages contain duplicate packages (with different versions)
451         """
452         for pkg in marked:
453             olderpkgnames = self.__find_older_pkgs(pkg)
454             for i in olderpkgnames:
455                 markednames = [str(x) for x in marked]
456                 if i in markednames:
457                     pkg.error('duplicate package: %s' % i)
458
459     def __rmolderfromsrc(self, test = False):
460         for pkg in self.marked4moving:
461             olderpkgnames = self.__find_older_pkgs(pkg)
462             for i in olderpkgnames:
463                 Pkg(i, self).remove(test)
464
465     def __rmotherfromdst(self, dsttree, test = False, archivetree = None):
466         for pkg in self.marked4moving:
467             pkgnames = self.__find_other_pkgs(pkg, dsttree)
468             for i in pkgnames:
469                 if archivetree == None:
470                     Pkg(i, dsttree).remove(test)
471                 else:
472                     Pkg(i, dsttree).move(archivetree, test = test)
473
474     # Used more than once filter functions
475     def __find_other_pkgs(self, pkg, tree):
476         escapedpkgname = pkg.name.replace('.', '\.').replace('+', '\+')
477         ziewre = re.compile(escapedpkgname + '-[^-]*-[^-]*$')
478         def filter_other_pkgs(x):
479             if ziewre.match(x) and not x == pkg.nvr:
480                 return True
481             else:
482                 return False
483         return filter(filter_other_pkgs, tree.pkgnames)
484
485     def __find_older_pkgs(self, pkg):
486         def filter_older_pkgs(x):
487             c = x.split('-')
488             rc = rpm.labelCompare(('0', pkg.version, pkg.release),
489                                                         ('0', c[-2], c[-1]))
490             if rc == 1: # pkg > x
491                 return True
492             else:
493                 return False
494         return filter(filter_older_pkgs, self.__find_other_pkgs(pkg, self))
495
496     def __checksigns(self, tree, pkgs, test = False):
497         """
498         Checks if pkgs in tree are all signed.
499
500         in case of test = true, error flag is set for unsigned packages
501         """
502         if not tree.treename in config.signed_trees:
503             return
504
505         for pkg in pkgs:
506             unsigned = 0
507             for file in pkg.rpmfiles():
508                 if not is_signed(file):
509                     unsigned += 1
510
511             if unsigned != 0:
512                 if test == True:
513                     if not quietmode:
514                         pkg.warning('%d files not signed' % unsigned)
515                 else:
516                     pkg.error('%d files not signed' % unsigned)
517
518     def __checkforobsoletes(self, tree, pkgs, test = False):
519         """
520         Checks queue file if package obsoletes something in destination tree and suggest for removal.
521
522         Only NAME tag is compared, i.e virtual packages do not get reported.
523
524         """
525         if test != True:
526             return
527
528         def findbyname(name):
529             def x(nvr):
530                 return '-'.join(nvr.split('-')[:-2]) == name
531             return filter(x, tree.pkgnames)
532
533         for pkg in pkgs:
534             obsoletes = pkg.obsoletes()
535             if not obsoletes:
536                 continue
537
538             for pn, setlist in obsoletes.items():
539                 for item in setlist:
540                     p = findbyname(item)
541                     if p:
542                         pkg.warning('obsoletes %s (via %s) in dest tree, perhaps you want rmpkg' % (p,pn))
543
544     def __checkforrelease(self, tree, pkgs, test = False):
545         """
546         Checks queue file if package release is non integer.
547
548         """
549         if test != True:
550             return
551
552         for pkg in pkgs:
553             if not pkg.is_release():
554                 pkg.warning('non-integer release: %s' % pkg.release)
This page took 0.079919 seconds and 2 git commands to generate.