]> git.pld-linux.org Git - projects/pld-ftp-admin.git/blame_incremental - modules/ftptree.py
- do not exit silently on errors. report and exit with failure
[projects/pld-ftp-admin.git] / modules / ftptree.py
... / ...
CommitLineData
1# vi: encoding=utf-8 ts=8 sts=4 sw=4 et
2
3import os, config, string, urllib, re, rpm
4from common import fileexists, noarchcachedir
5from baseftptree import BasePkg, BaseFtpTree
6from sign import is_signed
7
8errnum = 0
9quietmode = False
10
11class SomeError(Exception):
12 def __init__(self):
13 return
14
15 def __str__(self):
16 print "","An Error occured!"
17
18def bailoutonerror():
19 if not errnum == 0:
20 print "%d error(s) encountered... aborting" % errnum
21 raise SomeError
22
23def pinfo(msg):
24 print 'INFO: ' + msg
25
26def perror(msg):
27 global errnum
28 errnum = errnum + 1
29 print 'ERR: ' + msg
30
31def pwarning(msg):
32 print 'WARN: ' + msg
33
34def 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
45def 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
60class 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
260class 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.027935 seconds and 4 git commands to generate.