]> git.pld-linux.org Git - projects/pld-ftp-admin.git/blame - modules/ftptree.py
- signpkg: allow signing whole tree if no pkgname given
[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
MM
153 f.write("file:%s:%s\n" % (arch, rpm))
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()
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():
c5d9d3e9
AM
207 if not os.path.exists(rpmfile):
208 continue
f6dff636
ER
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
213a164a 222 def move(self, dsttree, test = False):
85f3481a 223 if dsttree.has_key(self.nvr):
8911f226 224 movedany = False
151d31cc 225 for arch in self.files.keys():
85f3481a 226 if arch in dsttree[self.nvr].files.keys():
77b616ee
AM
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))
151d31cc 231 for rpm in self.files[arch]:
930417e0 232 if self.is_debuginfo(rpm):
54ff0049
ER
233 rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
234 else:
235 rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
098f4a50 236 else:
8911f226
ER
237 movedany = True
238 dsttree[self.nvr].files[arch] = self.files[arch]
151d31cc 239 for rpm in self.files[arch]:
54ff0049
ER
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)
213a164a 244 if not test and movedany:
098f4a50 245 for bid in self.build.keys():
8911f226 246 dsttree[self.nvr].build[bid] = self.build[bid]
85f3481a 247 dsttree[self.nvr].writeinfo()
8911f226 248 rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
098f4a50 249 else:
8911f226 250 # move files
151d31cc
MM
251 for arch in self.files.keys():
252 for rpm in self.files[arch]:
54ff0049
ER
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)
098f4a50 257
8911f226
ER
258 # move metadata
259 mv(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', dsttree.basedir + '/SRPMS/.metadata/', test)
098f4a50 260
151d31cc 261class FtpTree(BaseFtpTree):
098f4a50 262 def __init__(self, tree, loadall=False):
151d31cc 263 BaseFtpTree.__init__(self, tree)
8911f226
ER
264 self.loadedpkgs = {}
265 self.marked4removal = []
266 self.marked4moving = []
267 self.pkgnames = []
d9b3388c 268 self.__loadpkgnames()
098f4a50 269 if loadall:
d9b3388c 270 for pkgname in self.pkgnames:
8911f226 271 self.loadedpkgs[pkgname] = Pkg(pkgname, self)
796b7867 272 # Tests:
8911f226 273 self.do_checkbuild = True
d9b3388c 274
098f4a50
MM
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
796b7867 284
098f4a50
MM
285 def has_key(self, key):
286 if key in self.pkgnames:
287 return True
288 else:
289 return False
796b7867 290
1610d209
MM
291 def keys(self):
292 return self.pkgnames
098f4a50 293
d7667ebe
MM
294 def values(self):
295 return self.loadedpkgs.values()
296
67b5bf38 297 def checktree(self, dsttree):
85f3481a
MM
298 self.__checkbuild(self.loadedpkgs.values())
299 self.__checkarchs(dsttree, self.loadedpkgs.values())
0767aa5e 300
213a164a 301 def testmove(self, dsttree, archivetree = None):
67b5bf38
MM
302 self.__checkbuild(self.marked4moving)
303 self.__checkarchs(dsttree, self.marked4moving)
332719f8 304 self.__checkduplicates(self.marked4moving)
aeb928ae
ER
305
306 self.__checksigns(dsttree, self.marked4moving, test = True)
f6dff636 307 self.__checkforobsoletes(dsttree, self.marked4moving, test = True)
8643d2cd 308 self.__checkforrelease(dsttree, self.marked4moving, test = True)
a0b52be0 309
de015730
ER
310 if not self.treename.count("archive"):
311 self.__rmolderfromsrc(test = True)
312
213a164a 313 self.__rmotherfromdst(dsttree, test = True, archivetree = archivetree)
a0b52be0
AM
314
315 for pkg in self.marked4moving:
8911f226 316 pkg.move(dsttree, test = True)
9b00920e 317
213a164a 318 def movepkgs(self, dsttree, archivetree = None):
f053c9e6 319 if self.do_checkbuild:
67b5bf38 320 self.__checkbuild(self.marked4moving)
098f4a50 321 bailoutonerror()
aeb928ae 322
67b5bf38 323 self.__checkarchs(dsttree, self.marked4moving)
098f4a50 324 bailoutonerror()
aeb928ae
ER
325
326 self.__checksigns(dsttree, self.marked4moving)
327 bailoutonerror()
328
de015730
ER
329 if not self.treename.count("archive"):
330 self.__rmolderfromsrc()
331
213a164a 332 self.__rmotherfromdst(dsttree, archivetree = archivetree)
098f4a50 333
14085d11 334 for pkg in self.marked4moving:
098f4a50
MM
335 pkg.move(dsttree)
336
ef010074 337 def rpmfiles(self, debugfiles = True, sourcefiles = True):
d38e8382
ER
338 if self.do_checkbuild:
339 self.__checkbuild(self.marked4moving)
d38e8382
ER
340
341 files = []
342 for pkg in self.marked4moving:
ef010074 343 files += pkg.rpmfiles(debugfiles = debugfiles, sourcefiles = sourcefiles)
d38e8382
ER
344 return files
345
14085d11 346 def removepkgs(self):
6bc6286e 347 if self.do_checkbuild:
67b5bf38 348 self.__checkbuild(self.marked4removal)
6bc6286e 349 bailoutonerror()
14085d11
MM
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
b36df4e6
ER
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)
14085d11 368
d9b3388c
MM
369 def __loadpkgnames(self):
370 def checkfiletype(name):
371 if name[-13:]=='.src.rpm.info':
372 return True
373 else:
374 return False
8911f226
ER
375 list = filter(checkfiletype, os.listdir(self.basedir+'/SRPMS/.metadata'))
376 self.pkgnames = map((lambda x: x[:-13]), list)
d9b3388c 377
14085d11 378 def __mark4something(self, wannabepkgs, markfunction):
098f4a50 379 def chopoffextension(pkg):
f6dff636
ER
380 found = pkg.find('.src.rpm')
381 if found == -1:
098f4a50
MM
382 return pkg
383 else:
384 return pkg[:found]
f6dff636 385
098f4a50 386 for wannabepkg in wannabepkgs:
f6dff636 387 pkgname = chopoffextension(wannabepkg)
098f4a50
MM
388 if pkgname in self.pkgnames:
389 if not pkgname in self.loadedpkgs.keys():
390 self.loadedpkgs[pkgname]=Pkg(pkgname, self)
14085d11 391 markfunction(self.loadedpkgs[pkgname])
098f4a50 392 else:
492b6398 393 perror('%s not found in source tree' % pkgname)
098f4a50 394 bailoutonerror()
14085d11 395
67b5bf38 396 def __checkbuild(self, marked):
8911f226
ER
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)
098f4a50 406 for i in re.findall(regb, f.read()):
8911f226
ER
407 if i[0] == 'g':
408 id = reid.sub(r'\1', i)
409 requests[id] = ""
098f4a50 410 elif i[0]=='b':
8911f226 411 requests[id] = requests[id] + i
098f4a50 412 f.close()
8911f226 413
6bc6286e 414 for pkg in marked:
098f4a50
MM
415 for bid in pkg.build.keys():
416 if requests.has_key(bid) and not requests[bid].find('?') == -1:
85f3481a 417 pkg.error("(buildid %s) building not finished" % bid)
098f4a50 418
67b5bf38 419 def __checkarchs(self, dsttree, marked):
8911f226
ER
420 """
421 Checks marked pkgs it is built on all archs.
422 """
67b5bf38 423 for pkg in marked:
4802753d 424 if len(pkg.files.keys()) <= 1:
85f3481a 425 pkg.error('has only src.rpm built')
4802753d 426 continue
8911f226
ER
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 = []
796b7867
MM
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:
b36df4e6 439 pkg.error('moving would remove archs: %s' % self.__arch_stringify(missingarchs))
8911f226
ER
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):
492b6398 443 continue
8911f226
ER
444 elif len(pkg.files.keys()) != len(config.ftp_archs) + 1:
445 missingarchs = []
492b6398
MM
446 for arch in config.ftp_archs:
447 if arch not in pkg.files.keys():
448 missingarchs.append(arch)
b36df4e6 449 pkg.warning('not built for archs: %s' % self.__arch_stringify(missingarchs))
098f4a50 450
332719f8 451 def __checkduplicates(self, marked):
d97ec2d6
AM
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:
406223be
AM
458 markednames = [str(x) for x in marked]
459 if i in markednames:
460 pkg.error('duplicate package: %s' % i)
d97ec2d6 461
213a164a 462 def __rmolderfromsrc(self, test = False):
14085d11 463 for pkg in self.marked4moving:
8911f226 464 olderpkgnames = self.__find_older_pkgs(pkg)
098f4a50 465 for i in olderpkgnames:
a0b52be0 466 Pkg(i, self).remove(test)
098f4a50 467
213a164a 468 def __rmotherfromdst(self, dsttree, test = False, archivetree = None):
14085d11 469 for pkg in self.marked4moving:
8911f226 470 pkgnames = self.__find_other_pkgs(pkg, dsttree)
098f4a50 471 for i in pkgnames:
213a164a
ER
472 if archivetree == None:
473 Pkg(i, dsttree).remove(test)
474 else:
475 Pkg(i, dsttree).move(archivetree, test = test)
098f4a50 476
d9b3388c 477 # Used more than once filter functions
d9b3388c 478 def __find_other_pkgs(self, pkg, tree):
8911f226
ER
479 escapedpkgname = pkg.name.replace('.', '\.').replace('+', '\+')
480 ziewre = re.compile(escapedpkgname + '-[^-]*-[^-]*$')
d9b3388c 481 def filter_other_pkgs(x):
85f3481a 482 if ziewre.match(x) and not x == pkg.nvr:
d9b3388c
MM
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):
8911f226 490 c = x.split('-')
ce470bd9
MM
491 rc = rpm.labelCompare(('0', pkg.version, pkg.release),
492 ('0', c[-2], c[-1]))
493 if rc == 1: # pkg > x
d9b3388c 494 return True
d9b3388c
MM
495 else:
496 return False
497 return filter(filter_older_pkgs, self.__find_other_pkgs(pkg, self))
498
aeb928ae
ER
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):
f6dff636 512 unsigned += 1
aeb928ae
ER
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)
f6dff636
ER
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:
9e047859 545 pkg.warning('obsoletes %s (via %s) in dest tree, perhaps you want rmpkg' % (p,pn))
8643d2cd
ER
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 2.986244 seconds and 4 git commands to generate.