]> git.pld-linux.org Git - projects/pld-ftp-admin.git/blame - modules/ftptree.py
- do not exit silently on errors. report and exit with failure
[projects/pld-ftp-admin.git] / modules / ftptree.py
CommitLineData
098f4a50
MM
1# vi: encoding=utf-8 ts=8 sts=4 sw=4 et
2
ce470bd9 3import os, config, string, urllib, re, rpm
81e9387d 4from common import fileexists, noarchcachedir
151d31cc 5from baseftptree import BasePkg, BaseFtpTree
aeb928ae 6from sign import is_signed
098f4a50 7
aeb928ae
ER
8errnum = 0
9quietmode = False
0a108b7f 10
76c9cb26 11class SomeError(Exception):
db126e0b
ER
12 def __init__(self):
13 return
14
15 def __str__(self):
16 print "","An Error occured!"
17
098f4a50
MM
18def bailoutonerror():
19 if not errnum == 0:
20 print "%d error(s) encountered... aborting" % errnum
76c9cb26 21 raise SomeError
098f4a50 22
d9b3388c 23def pinfo(msg):
0767aa5e 24 print 'INFO: ' + msg
d9b3388c 25
098f4a50
MM
26def perror(msg):
27 global errnum
213a164a 28 errnum = errnum + 1
492b6398 29 print 'ERR: ' + msg
098f4a50 30
796b7867 31def pwarning(msg):
492b6398 32 print 'WARN: ' + msg
796b7867 33
213a164a 34def rm(file, test = False):
a0b52be0
AM
35 if test:
36 if not os.path.exists(file):
37 pinfo("TEST os.remove(%s): file doesn't exists" % file)
38 else:
2d70dbd8
AM
39 try:
40 os.remove(file)
41 except OSError, e:
42 pinfo("os.remove(%s): %s" % (file, e))
8911f226 43 #raise
a0b52be0 44
213a164a 45def mv(src, dst, test = False):
a0b52be0 46 fsrc = src
213a164a 47 fdst = dst + '/' + src.split('/')[-1]
a0b52be0
AM
48 if test:
49 if not os.path.exists(src):
50 pinfo("TEST os.rename(%s, %s): source doesn't exists" % (fsrc, fdst))
c4647bc3
ER
51 if not os.path.exists(dst):
52 pinfo("TEST destination doesn't exist: %s" % dst)
a0b52be0
AM
53 else:
54 try:
55 os.rename(fsrc, fdst)
56 except OSError, e:
aee80850 57 pinfo("os.rename(%s, %s): %s" % (fsrc, fdst, e))
a0b52be0 58 raise
098f4a50 59
151d31cc 60class Pkg(BasePkg):
85f3481a
MM
61 def __init__(self, nvr, tree):
62 BasePkg.__init__(self, nvr, tree)
8911f226
ER
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 = []
098f4a50 71
d7667ebe
MM
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
54ff0049
ER
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
ef010074
ER
93 def is_sourcefile(self, file):
94 """
95 returns true if file is source package
96 """
97 return file[-8:] == '.src.rpm'
98
8643d2cd
ER
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 """
1af4af39 110 return self.release.count('.') % 2 == 0
8643d2cd 111
14085d11
MM
112 def mark4moving(self):
113 if not self.marked4moving:
f301e305
MM
114 # Only one pkg in this pool can be marked for moving
115 for pkg in self.marked4movingpool:
116 pkg.unmark4moving()
14085d11
MM
117 self.tree.marked4moving.append(self)
118 self.marked4moving=True
119
f301e305
MM
120 def unmark4moving(self):
121 if self.marked4moving:
122 self.tree.marked4moving.remove(self)
123 self.marked4moving=False
124
14085d11
MM
125 def mark4removal(self):
126 if not self.marked4removal:
127 self.tree.marked4removal.append(self)
128 self.marked4removal=True
098f4a50 129
85f3481a
MM
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
bb2cb325
MM
140 def load(self, content=None):
141 BasePkg.load(self, content)
098f4a50 142 if self.info.has_key('move'):
14085d11 143 self.mark4moving()
098f4a50
MM
144
145 def writeinfo(self):
8911f226 146 f = open(self.tree.basedir+'/SRPMS/.metadata/'+self.nvr+'.src.rpm.info', 'w')
098f4a50
MM
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], ':')))
151d31cc
MM
151 for arch in self.files.keys():
152 for rpm in self.files[arch]:
098f4a50 153 f.write("file:%s:%s\n" % (arch, rpm))
e50c8ad1 154
8911f226
ER
155 def remove(self, test = False):
156 """
157 Remove package from ftp
158 """
151d31cc
MM
159 for arch in self.files.keys():
160 for rpm in self.files[arch]:
54ff0049
ER
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)
8911f226
ER
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)
098f4a50 171
ef010074 172 def rpmfiles(self, debugfiles = True, sourcefiles = True):
d38e8382
ER
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):
3dd276bd
ER
180 if debugfiles:
181 files.append(self.tree.basedir + '/' + arch + '/debuginfo/' + nvr)
d38e8382 182 else:
ef010074
ER
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)
d38e8382
ER
188 return files
189
f6dff636
ER
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()
f6dff636
ER
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():
c5d9d3e9
AM
206 if not os.path.exists(rpmfile):
207 continue
f6dff636
ER
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
213a164a 221 def move(self, dsttree, test = False):
85f3481a 222 if dsttree.has_key(self.nvr):
8911f226 223 movedany = False
151d31cc 224 for arch in self.files.keys():
85f3481a 225 if arch in dsttree[self.nvr].files.keys():
77b616ee
AM
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))
151d31cc 230 for rpm in self.files[arch]:
930417e0 231 if self.is_debuginfo(rpm):
54ff0049
ER
232 rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
233 else:
234 rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
098f4a50 235 else:
8911f226
ER
236 movedany = True
237 dsttree[self.nvr].files[arch] = self.files[arch]
151d31cc 238 for rpm in self.files[arch]:
54ff0049
ER
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)
213a164a 243 if not test and movedany:
098f4a50 244 for bid in self.build.keys():
8911f226 245 dsttree[self.nvr].build[bid] = self.build[bid]
85f3481a 246 dsttree[self.nvr].writeinfo()
8911f226 247 rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
098f4a50 248 else:
8911f226 249 # move files
151d31cc
MM
250 for arch in self.files.keys():
251 for rpm in self.files[arch]:
54ff0049
ER
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)
098f4a50 256
8911f226
ER
257 # move metadata
258 mv(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', dsttree.basedir + '/SRPMS/.metadata/', test)
098f4a50 259
151d31cc 260class FtpTree(BaseFtpTree):
098f4a50 261 def __init__(self, tree, loadall=False):
151d31cc 262 BaseFtpTree.__init__(self, tree)
8911f226
ER
263 self.loadedpkgs = {}
264 self.marked4removal = []
265 self.marked4moving = []
266 self.pkgnames = []
d9b3388c 267 self.__loadpkgnames()
098f4a50 268 if loadall:
d9b3388c 269 for pkgname in self.pkgnames:
8911f226 270 self.loadedpkgs[pkgname] = Pkg(pkgname, self)
796b7867 271 # Tests:
8911f226 272 self.do_checkbuild = True
d9b3388c 273
098f4a50
MM
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
796b7867 283
098f4a50
MM
284 def has_key(self, key):
285 if key in self.pkgnames:
286 return True
287 else:
288 return False
796b7867 289
1610d209
MM
290 def keys(self):
291 return self.pkgnames
098f4a50 292
d7667ebe
MM
293 def values(self):
294 return self.loadedpkgs.values()
295
67b5bf38 296 def checktree(self, dsttree):
85f3481a
MM
297 self.__checkbuild(self.loadedpkgs.values())
298 self.__checkarchs(dsttree, self.loadedpkgs.values())
0767aa5e 299
213a164a 300 def testmove(self, dsttree, archivetree = None):
67b5bf38
MM
301 self.__checkbuild(self.marked4moving)
302 self.__checkarchs(dsttree, self.marked4moving)
332719f8 303 self.__checkduplicates(self.marked4moving)
aeb928ae
ER
304
305 self.__checksigns(dsttree, self.marked4moving, test = True)
f6dff636 306 self.__checkforobsoletes(dsttree, self.marked4moving, test = True)
8643d2cd 307 self.__checkforrelease(dsttree, self.marked4moving, test = True)
461bc5ab 308
de015730
ER
309 if not self.treename.count("archive"):
310 self.__rmolderfromsrc(test = True)
311
213a164a 312 self.__rmotherfromdst(dsttree, test = True, archivetree = archivetree)
a0b52be0
AM
313
314 for pkg in self.marked4moving:
8911f226 315 pkg.move(dsttree, test = True)
9b00920e 316
213a164a 317 def movepkgs(self, dsttree, archivetree = None):
f053c9e6 318 if self.do_checkbuild:
67b5bf38 319 self.__checkbuild(self.marked4moving)
098f4a50 320 bailoutonerror()
aeb928ae 321
67b5bf38 322 self.__checkarchs(dsttree, self.marked4moving)
098f4a50 323 bailoutonerror()
aeb928ae
ER
324
325 self.__checksigns(dsttree, self.marked4moving)
326 bailoutonerror()
327
de015730
ER
328 if not self.treename.count("archive"):
329 self.__rmolderfromsrc()
330
213a164a 331 self.__rmotherfromdst(dsttree, archivetree = archivetree)
098f4a50 332
14085d11 333 for pkg in self.marked4moving:
098f4a50
MM
334 pkg.move(dsttree)
335
ef010074 336 def rpmfiles(self, debugfiles = True, sourcefiles = True):
d38e8382
ER
337 if self.do_checkbuild:
338 self.__checkbuild(self.marked4moving)
d38e8382
ER
339
340 files = []
341 for pkg in self.marked4moving:
ef010074 342 files += pkg.rpmfiles(debugfiles = debugfiles, sourcefiles = sourcefiles)
d38e8382
ER
343 return files
344
14085d11 345 def removepkgs(self):
6bc6286e 346 if self.do_checkbuild:
67b5bf38 347 self.__checkbuild(self.marked4removal)
6bc6286e 348 bailoutonerror()
14085d11
MM
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)
14085d11
MM
357
358 # Internal functions below
b36df4e6
ER
359 def __arch_stringify(self, list):
360 ret = []
d886975e 361 dist = config.ftp_dist;
b36df4e6
ER
362 for arch in list:
363 ret.append(dist + '-' + arch)
364 return ' '.join(ret)
14085d11 365
d9b3388c
MM
366 def __loadpkgnames(self):
367 def checkfiletype(name):
368 if name[-13:]=='.src.rpm.info':
369 return True
370 else:
371 return False
8911f226
ER
372 list = filter(checkfiletype, os.listdir(self.basedir+'/SRPMS/.metadata'))
373 self.pkgnames = map((lambda x: x[:-13]), list)
d9b3388c 374
14085d11 375 def __mark4something(self, wannabepkgs, markfunction):
098f4a50 376 def chopoffextension(pkg):
f6dff636
ER
377 found = pkg.find('.src.rpm')
378 if found == -1:
098f4a50
MM
379 return pkg
380 else:
381 return pkg[:found]
f6dff636 382
098f4a50 383 for wannabepkg in wannabepkgs:
f6dff636 384 pkgname = chopoffextension(wannabepkg)
098f4a50
MM
385 if pkgname in self.pkgnames:
386 if not pkgname in self.loadedpkgs.keys():
387 self.loadedpkgs[pkgname]=Pkg(pkgname, self)
14085d11 388 markfunction(self.loadedpkgs[pkgname])
098f4a50 389 else:
492b6398 390 perror('%s not found in source tree' % pkgname)
098f4a50 391 bailoutonerror()
14085d11 392
67b5bf38 393 def __checkbuild(self, marked):
8911f226
ER
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)
098f4a50 403 for i in re.findall(regb, f.read()):
8911f226
ER
404 if i[0] == 'g':
405 id = reid.sub(r'\1', i)
406 requests[id] = ""
098f4a50 407 elif i[0]=='b':
8911f226 408 requests[id] = requests[id] + i
098f4a50 409 f.close()
8911f226 410
6bc6286e 411 for pkg in marked:
098f4a50
MM
412 for bid in pkg.build.keys():
413 if requests.has_key(bid) and not requests[bid].find('?') == -1:
85f3481a 414 pkg.error("(buildid %s) building not finished" % bid)
098f4a50 415
67b5bf38 416 def __checkarchs(self, dsttree, marked):
8911f226
ER
417 """
418 Checks marked pkgs it is built on all archs.
419 """
67b5bf38 420 for pkg in marked:
4802753d 421 if len(pkg.files.keys()) <= 1:
85f3481a 422 pkg.error('has only src.rpm built')
4802753d 423 continue
8911f226
ER
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 = []
796b7867
MM
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:
b36df4e6 436 pkg.error('moving would remove archs: %s' % self.__arch_stringify(missingarchs))
8911f226
ER
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):
492b6398 440 continue
8911f226
ER
441 elif len(pkg.files.keys()) != len(config.ftp_archs) + 1:
442 missingarchs = []
492b6398
MM
443 for arch in config.ftp_archs:
444 if arch not in pkg.files.keys():
445 missingarchs.append(arch)
b36df4e6 446 pkg.warning('not built for archs: %s' % self.__arch_stringify(missingarchs))
098f4a50 447
332719f8 448 def __checkduplicates(self, marked):
d97ec2d6
AM
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:
406223be
AM
455 markednames = [str(x) for x in marked]
456 if i in markednames:
457 pkg.error('duplicate package: %s' % i)
d97ec2d6 458
213a164a 459 def __rmolderfromsrc(self, test = False):
14085d11 460 for pkg in self.marked4moving:
8911f226 461 olderpkgnames = self.__find_older_pkgs(pkg)
098f4a50 462 for i in olderpkgnames:
a0b52be0 463 Pkg(i, self).remove(test)
098f4a50 464
213a164a 465 def __rmotherfromdst(self, dsttree, test = False, archivetree = None):
14085d11 466 for pkg in self.marked4moving:
8911f226 467 pkgnames = self.__find_other_pkgs(pkg, dsttree)
098f4a50 468 for i in pkgnames:
213a164a
ER
469 if archivetree == None:
470 Pkg(i, dsttree).remove(test)
471 else:
472 Pkg(i, dsttree).move(archivetree, test = test)
098f4a50 473
d9b3388c 474 # Used more than once filter functions
d9b3388c 475 def __find_other_pkgs(self, pkg, tree):
8911f226
ER
476 escapedpkgname = pkg.name.replace('.', '\.').replace('+', '\+')
477 ziewre = re.compile(escapedpkgname + '-[^-]*-[^-]*$')
d9b3388c 478 def filter_other_pkgs(x):
85f3481a 479 if ziewre.match(x) and not x == pkg.nvr:
d9b3388c
MM
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):
8911f226 487 c = x.split('-')
ce470bd9
MM
488 rc = rpm.labelCompare(('0', pkg.version, pkg.release),
489 ('0', c[-2], c[-1]))
490 if rc == 1: # pkg > x
d9b3388c 491 return True
d9b3388c
MM
492 else:
493 return False
494 return filter(filter_older_pkgs, self.__find_other_pkgs(pkg, self))
495
aeb928ae
ER
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):
f6dff636 509 unsigned += 1
aeb928ae
ER
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)
f6dff636
ER
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:
9e047859 542 pkg.warning('obsoletes %s (via %s) in dest tree, perhaps you want rmpkg' % (p,pn))
8643d2cd
ER
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.181928 seconds and 4 git commands to generate.