]> git.pld-linux.org Git - projects/pld-ftp-admin.git/blame - modules/ftptree.py
Duplicate packages with different version or release are considered to be errors.
[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
8643d2cd
ER
93 # returns true if package build is integer
94 def is_release(self):
95 """
96 To account Release tags with subver macros, we consider integer release
97 if it contains odd number of dots:
98
99 1 -> True
100 0.1 -> False
101 0.%{subver}.%{rel}, %{rel} = 1 -> 0.20010.1 -> True
102 0.%{subver}.%{rel}, %{rel} = 0.1 -> 0.20010.0.1 -> False
103 """
1af4af39 104 return self.release.count('.') % 2 == 0
8643d2cd 105
14085d11
MM
106 def mark4moving(self):
107 if not self.marked4moving:
f301e305
MM
108 # Only one pkg in this pool can be marked for moving
109 for pkg in self.marked4movingpool:
110 pkg.unmark4moving()
14085d11
MM
111 self.tree.marked4moving.append(self)
112 self.marked4moving=True
113
f301e305
MM
114 def unmark4moving(self):
115 if self.marked4moving:
116 self.tree.marked4moving.remove(self)
117 self.marked4moving=False
118
14085d11
MM
119 def mark4removal(self):
120 if not self.marked4removal:
121 self.tree.marked4removal.append(self)
122 self.marked4removal=True
098f4a50 123
85f3481a
MM
124 def error(self, msg):
125 self.errors.append(msg)
126 if not quietmode:
127 perror('%s %s' % (self.nvr, msg))
128
129 def warning(self, msg):
130 self.warnings.append(msg)
131 if not quietmode:
132 pwarning('%s %s' % (self.nvr, msg))
133
bb2cb325
MM
134 def load(self, content=None):
135 BasePkg.load(self, content)
098f4a50 136 if self.info.has_key('move'):
14085d11 137 self.mark4moving()
098f4a50
MM
138
139 def writeinfo(self):
8911f226 140 f = open(self.tree.basedir+'/SRPMS/.metadata/'+self.nvr+'.src.rpm.info', 'w')
098f4a50
MM
141 for bid in self.build.keys():
142 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))
143 for key in self.info.keys():
144 f.write("info:%s:%s\n" % (key, string.join(self.info[key], ':')))
151d31cc
MM
145 for arch in self.files.keys():
146 for rpm in self.files[arch]:
098f4a50
MM
147 f.write("file:%s:%s\n" % (arch, rpm))
148
8911f226
ER
149 def remove(self, test = False):
150 """
151 Remove package from ftp
152 """
151d31cc
MM
153 for arch in self.files.keys():
154 for rpm in self.files[arch]:
54ff0049
ER
155 if self.is_debuginfo(rpm):
156 rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
157 else:
158 rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
8911f226
ER
159 if arch == 'noarch':
160 if fileexists(noarchcachedir + rpm + '.filelist'):
161 rm(noarchcachedir + rpm + '.filelist', test)
162 if fileexists(noarchcachedir + rpm + '.reqlist'):
163 rm(noarchcachedir + rpm + '.reqlist', test)
164 rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
098f4a50 165
3dd276bd 166 def rpmfiles(self, debugfiles = True):
d38e8382
ER
167 """
168 Return rpm files related to this package
169 """
170 files = []
171 for arch, rpms in self.files.items():
172 for nvr in rpms:
173 if self.is_debuginfo(nvr):
3dd276bd
ER
174 if debugfiles:
175 files.append(self.tree.basedir + '/' + arch + '/debuginfo/' + nvr)
d38e8382
ER
176 else:
177 files.append(self.tree.basedir + '/' + arch + '/RPMS/' + nvr)
178 return files
179
f6dff636
ER
180 def obsoletes(self):
181 """
182 Return obsoletes for all packages in Pkg:
183
184 {'php-geshi': set(['geshi'])}
185
186 """
187 def rpmhdr(pkg):
188 ts = rpm.ts()
189 ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
190 fdno = os.open(pkg, os.O_RDONLY)
191 hdr = ts.hdrFromFdno(fdno)
192 os.close(fdno)
193 return hdr
194
195 obsoletes = {}
196 for rpmfile in self.rpmfiles():
c5d9d3e9
AM
197 if not os.path.exists(rpmfile):
198 continue
f6dff636
ER
199 hdr = rpmhdr(rpmfile)
200 if not hdr[rpm.RPMTAG_OBSOLETES]:
201 continue
202
203 name = hdr[rpm.RPMTAG_NAME]
204 if not name in obsoletes:
205 obsoletes[name] = set()
206
207 for tag in hdr[rpm.RPMTAG_OBSOLETES]:
208 obsoletes[name].add(tag)
209
210 return obsoletes
211
213a164a 212 def move(self, dsttree, test = False):
85f3481a 213 if dsttree.has_key(self.nvr):
8911f226 214 movedany = False
151d31cc 215 for arch in self.files.keys():
85f3481a 216 if arch in dsttree[self.nvr].files.keys():
77b616ee
AM
217 msg = ""
218 if test:
219 msg = "TEST "
220 pinfo("%sArch %s for %s is already present in dest tree; removing from srctree" % (msg, arch, self.nvr))
151d31cc 221 for rpm in self.files[arch]:
3dd276bd 222 if self.il-page-warningus_debuginfo(rpm):
54ff0049
ER
223 rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
224 else:
225 rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
098f4a50 226 else:
8911f226
ER
227 movedany = True
228 dsttree[self.nvr].files[arch] = self.files[arch]
151d31cc 229 for rpm in self.files[arch]:
54ff0049
ER
230 if self.is_debuginfo(rpm):
231 mv(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, dsttree.basedir + '/' + arch + '/debuginfo/', test)
232 else:
233 mv(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, dsttree.basedir + '/' + arch + '/RPMS/', test)
213a164a 234 if not test and movedany:
098f4a50 235 for bid in self.build.keys():
8911f226 236 dsttree[self.nvr].build[bid] = self.build[bid]
85f3481a 237 dsttree[self.nvr].writeinfo()
8911f226 238 rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
098f4a50 239 else:
8911f226 240 # move files
151d31cc
MM
241 for arch in self.files.keys():
242 for rpm in self.files[arch]:
54ff0049
ER
243 if self.is_debuginfo(rpm):
244 mv(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, dsttree.basedir + '/' + arch + '/debuginfo/', test)
245 else:
246 mv(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, dsttree.basedir + '/' + arch + '/RPMS/', test)
098f4a50 247
8911f226
ER
248 # move metadata
249 mv(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', dsttree.basedir + '/SRPMS/.metadata/', test)
098f4a50 250
151d31cc 251class FtpTree(BaseFtpTree):
098f4a50 252 def __init__(self, tree, loadall=False):
151d31cc 253 BaseFtpTree.__init__(self, tree)
8911f226
ER
254 self.loadedpkgs = {}
255 self.marked4removal = []
256 self.marked4moving = []
257 self.pkgnames = []
d9b3388c 258 self.__loadpkgnames()
098f4a50 259 if loadall:
d9b3388c 260 for pkgname in self.pkgnames:
8911f226 261 self.loadedpkgs[pkgname] = Pkg(pkgname, self)
796b7867 262 # Tests:
8911f226 263 self.do_checkbuild = True
d9b3388c 264
098f4a50
MM
265 def __getitem__(self, key):
266 if self.loadedpkgs.has_key(key):
267 return self.loadedpkgs[key]
268 elif key in self.pkgnames:
269 pkg=Pkg(key, self)
270 self.loadedpkgs[key]=pkg
271 return pkg
272 else:
273 raise KeyError, key
796b7867 274
098f4a50
MM
275 def has_key(self, key):
276 if key in self.pkgnames:
277 return True
278 else:
279 return False
796b7867 280
1610d209
MM
281 def keys(self):
282 return self.pkgnames
098f4a50 283
d7667ebe
MM
284 def values(self):
285 return self.loadedpkgs.values()
286
67b5bf38 287 def checktree(self, dsttree):
85f3481a
MM
288 self.__checkbuild(self.loadedpkgs.values())
289 self.__checkarchs(dsttree, self.loadedpkgs.values())
0767aa5e 290
213a164a 291 def testmove(self, dsttree, archivetree = None):
67b5bf38
MM
292 self.__checkbuild(self.marked4moving)
293 self.__checkarchs(dsttree, self.marked4moving)
d97ec2d6 294 self.__checduplicates(self.marked4moving)
aeb928ae
ER
295
296 self.__checksigns(dsttree, self.marked4moving, test = True)
f6dff636 297 self.__checkforobsoletes(dsttree, self.marked4moving, test = True)
8643d2cd 298 self.__checkforrelease(dsttree, self.marked4moving, test = True)
a0b52be0 299
8911f226 300 self.__rmolderfromsrc(test = True)
213a164a 301 self.__rmotherfromdst(dsttree, test = True, archivetree = archivetree)
a0b52be0
AM
302
303 for pkg in self.marked4moving:
8911f226 304 pkg.move(dsttree, test = True)
9b00920e 305
213a164a 306 def movepkgs(self, dsttree, archivetree = None):
f053c9e6 307 if self.do_checkbuild:
67b5bf38 308 self.__checkbuild(self.marked4moving)
098f4a50 309 bailoutonerror()
aeb928ae 310
67b5bf38 311 self.__checkarchs(dsttree, self.marked4moving)
098f4a50 312 bailoutonerror()
aeb928ae
ER
313
314 self.__checksigns(dsttree, self.marked4moving)
315 bailoutonerror()
316
098f4a50 317 self.__rmolderfromsrc()
213a164a 318 self.__rmotherfromdst(dsttree, archivetree = archivetree)
098f4a50 319
14085d11 320 for pkg in self.marked4moving:
098f4a50
MM
321 pkg.move(dsttree)
322
d38e8382
ER
323 def rpmfiles(self):
324 if self.do_checkbuild:
325 self.__checkbuild(self.marked4moving)
d38e8382
ER
326
327 files = []
328 for pkg in self.marked4moving:
329 files += pkg.rpmfiles()
330 return files
331
14085d11 332 def removepkgs(self):
6bc6286e 333 if self.do_checkbuild:
67b5bf38 334 self.__checkbuild(self.marked4removal)
6bc6286e 335 bailoutonerror()
14085d11
MM
336 for pkg in self.marked4removal:
337 pkg.remove()
338
339 def mark4removal(self, wannabepkgs):
340 self.__mark4something(wannabepkgs, Pkg.mark4removal)
341
342 def mark4moving(self, wannabepkgs):
343 self.__mark4something(wannabepkgs, Pkg.mark4moving)
344
345
346 # Internal functions below
b36df4e6
ER
347 def __arch_stringify(self, list):
348 ret = []
349 # XXX: is dist line in any config?
350 dist = 'ac'
351 for arch in list:
352 ret.append(dist + '-' + arch)
353 return ' '.join(ret)
14085d11 354
d9b3388c
MM
355 def __loadpkgnames(self):
356 def checkfiletype(name):
357 if name[-13:]=='.src.rpm.info':
358 return True
359 else:
360 return False
8911f226
ER
361 list = filter(checkfiletype, os.listdir(self.basedir+'/SRPMS/.metadata'))
362 self.pkgnames = map((lambda x: x[:-13]), list)
d9b3388c 363
14085d11 364 def __mark4something(self, wannabepkgs, markfunction):
098f4a50 365 def chopoffextension(pkg):
f6dff636
ER
366 found = pkg.find('.src.rpm')
367 if found == -1:
098f4a50
MM
368 return pkg
369 else:
370 return pkg[:found]
f6dff636 371
098f4a50 372 for wannabepkg in wannabepkgs:
f6dff636 373 pkgname = chopoffextension(wannabepkg)
098f4a50
MM
374 if pkgname in self.pkgnames:
375 if not pkgname in self.loadedpkgs.keys():
376 self.loadedpkgs[pkgname]=Pkg(pkgname, self)
14085d11 377 markfunction(self.loadedpkgs[pkgname])
098f4a50 378 else:
492b6398 379 perror('%s not found in source tree' % pkgname)
098f4a50 380 bailoutonerror()
14085d11 381
67b5bf38 382 def __checkbuild(self, marked):
8911f226
ER
383 """
384 Checks queue file if all arches are built
385
386 Reads config.builderqueue to grab the info
387 """
388 f = urllib.urlopen(config.builderqueue)
389 requests = {}
390 reid = re.compile(r'^.*id=(.*) pri.*$')
391 regb = re.compile(r'^group:.*$|builders:.*$', re.M)
098f4a50 392 for i in re.findall(regb, f.read()):
8911f226
ER
393 if i[0] == 'g':
394 id = reid.sub(r'\1', i)
395 requests[id] = ""
098f4a50 396 elif i[0]=='b':
8911f226 397 requests[id] = requests[id] + i
098f4a50 398 f.close()
8911f226 399
6bc6286e 400 for pkg in marked:
098f4a50
MM
401 for bid in pkg.build.keys():
402 if requests.has_key(bid) and not requests[bid].find('?') == -1:
85f3481a 403 pkg.error("(buildid %s) building not finished" % bid)
098f4a50 404
67b5bf38 405 def __checkarchs(self, dsttree, marked):
8911f226
ER
406 """
407 Checks marked pkgs it is built on all archs.
408 """
67b5bf38 409 for pkg in marked:
4802753d 410 if len(pkg.files.keys()) <= 1:
85f3481a 411 pkg.error('has only src.rpm built')
4802753d 412 continue
8911f226
ER
413 otherpkgnames = self.__find_other_pkgs(pkg, dsttree)
414
415 # check if we're not removing some archs
416 if otherpkgnames:
417 curarchs = []
418 missingarchs = []
796b7867
MM
419 for somepkg in otherpkgnames:
420 curarchs.extend(Pkg(somepkg, dsttree).files.keys())
421 for arch in curarchs:
422 if arch not in pkg.files.keys():
423 missingarchs.append(arch)
424 if missingarchs:
b36df4e6 425 pkg.error('moving would remove archs: %s' % self.__arch_stringify(missingarchs))
8911f226
ER
426 else:
427 # warn if a package isn't built for all archs
428 if (config.separate_noarch and 'noarch' in pkg.files.keys() and len(pkg.files.keys()) == 2):
492b6398 429 continue
8911f226
ER
430 elif len(pkg.files.keys()) != len(config.ftp_archs) + 1:
431 missingarchs = []
492b6398
MM
432 for arch in config.ftp_archs:
433 if arch not in pkg.files.keys():
434 missingarchs.append(arch)
b36df4e6 435 pkg.warning('not built for archs: %s' % self.__arch_stringify(missingarchs))
098f4a50 436
d97ec2d6
AM
437 def __checduplicates(self, marked):
438 """
439 Checks if marked packages contain duplicate packages (with different versions)
440 """
441 for pkg in marked:
442 olderpkgnames = self.__find_older_pkgs(pkg)
443 for i in olderpkgnames:
444 pkg.error('duplicate package: %s' % i)
445
213a164a 446 def __rmolderfromsrc(self, test = False):
14085d11 447 for pkg in self.marked4moving:
8911f226 448 olderpkgnames = self.__find_older_pkgs(pkg)
098f4a50 449 for i in olderpkgnames:
a0b52be0 450 Pkg(i, self).remove(test)
098f4a50 451
213a164a 452 def __rmotherfromdst(self, dsttree, test = False, archivetree = None):
14085d11 453 for pkg in self.marked4moving:
8911f226 454 pkgnames = self.__find_other_pkgs(pkg, dsttree)
098f4a50 455 for i in pkgnames:
213a164a
ER
456 if archivetree == None:
457 Pkg(i, dsttree).remove(test)
458 else:
459 Pkg(i, dsttree).move(archivetree, test = test)
098f4a50 460
d9b3388c 461 # Used more than once filter functions
d9b3388c 462 def __find_other_pkgs(self, pkg, tree):
8911f226
ER
463 escapedpkgname = pkg.name.replace('.', '\.').replace('+', '\+')
464 ziewre = re.compile(escapedpkgname + '-[^-]*-[^-]*$')
d9b3388c 465 def filter_other_pkgs(x):
85f3481a 466 if ziewre.match(x) and not x == pkg.nvr:
d9b3388c
MM
467 return True
468 else:
469 return False
470 return filter(filter_other_pkgs, tree.pkgnames)
471
472 def __find_older_pkgs(self, pkg):
473 def filter_older_pkgs(x):
8911f226 474 c = x.split('-')
ce470bd9
MM
475 rc = rpm.labelCompare(('0', pkg.version, pkg.release),
476 ('0', c[-2], c[-1]))
477 if rc == 1: # pkg > x
d9b3388c 478 return True
d9b3388c
MM
479 else:
480 return False
481 return filter(filter_older_pkgs, self.__find_other_pkgs(pkg, self))
482
aeb928ae
ER
483 def __checksigns(self, tree, pkgs, test = False):
484 """
485 Checks if pkgs in tree are all signed.
486
487 in case of test = true, error flag is set for unsigned packages
488 """
489 if not tree.treename in config.signed_trees:
490 return
491
492 for pkg in pkgs:
493 unsigned = 0
494 for file in pkg.rpmfiles():
495 if not is_signed(file):
f6dff636 496 unsigned += 1
aeb928ae
ER
497
498 if unsigned != 0:
499 if test == True:
500 if not quietmode:
501 pkg.warning('%d files not signed' % unsigned)
502 else:
503 pkg.error('%d files not signed' % unsigned)
f6dff636
ER
504
505 def __checkforobsoletes(self, tree, pkgs, test = False):
506 """
507 Checks queue file if package obsoletes something in destination tree and suggest for removal.
508
509 Only NAME tag is compared, i.e virtual packages do not get reported.
510
511 """
512 if test != True:
513 return
514
515 def findbyname(name):
516 def x(nvr):
517 return '-'.join(nvr.split('-')[:-2]) == name
518 return filter(x, tree.pkgnames)
519
520 for pkg in pkgs:
521 obsoletes = pkg.obsoletes()
522 if not obsoletes:
523 continue
524
525 for pn, setlist in obsoletes.items():
526 for item in setlist:
527 p = findbyname(item)
528 if p:
9e047859 529 pkg.warning('obsoletes %s (via %s) in dest tree, perhaps you want rmpkg' % (p,pn))
8643d2cd
ER
530
531 def __checkforrelease(self, tree, pkgs, test = False):
532 """
533 Checks queue file if package release is non integer.
534
535 """
536 if test != True:
537 return
538
539 for pkg in pkgs:
540 if not pkg.is_release():
541 pkg.warning('non-integer release: %s' % pkg.release)
This page took 4.149673 seconds and 4 git commands to generate.