]> git.pld-linux.org Git - projects/pld-ftp-admin.git/blame - modules/ftptree.py
- reorganize to allow sudo for any user
[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]:
3dd276bd 232 if self.il-page-warningus_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
8911f226 310 self.__rmolderfromsrc(test = True)
213a164a 311 self.__rmotherfromdst(dsttree, test = True, archivetree = archivetree)
a0b52be0
AM
312
313 for pkg in self.marked4moving:
8911f226 314 pkg.move(dsttree, test = True)
9b00920e 315
213a164a 316 def movepkgs(self, dsttree, archivetree = None):
f053c9e6 317 if self.do_checkbuild:
67b5bf38 318 self.__checkbuild(self.marked4moving)
098f4a50 319 bailoutonerror()
aeb928ae 320
67b5bf38 321 self.__checkarchs(dsttree, self.marked4moving)
098f4a50 322 bailoutonerror()
aeb928ae
ER
323
324 self.__checksigns(dsttree, self.marked4moving)
325 bailoutonerror()
326
098f4a50 327 self.__rmolderfromsrc()
213a164a 328 self.__rmotherfromdst(dsttree, archivetree = archivetree)
098f4a50 329
14085d11 330 for pkg in self.marked4moving:
098f4a50
MM
331 pkg.move(dsttree)
332
ef010074 333 def rpmfiles(self, debugfiles = True, sourcefiles = True):
d38e8382
ER
334 if self.do_checkbuild:
335 self.__checkbuild(self.marked4moving)
d38e8382
ER
336
337 files = []
338 for pkg in self.marked4moving:
ef010074 339 files += pkg.rpmfiles(debugfiles = debugfiles, sourcefiles = sourcefiles)
d38e8382
ER
340 return files
341
14085d11 342 def removepkgs(self):
6bc6286e 343 if self.do_checkbuild:
67b5bf38 344 self.__checkbuild(self.marked4removal)
6bc6286e 345 bailoutonerror()
14085d11
MM
346 for pkg in self.marked4removal:
347 pkg.remove()
348
349 def mark4removal(self, wannabepkgs):
350 self.__mark4something(wannabepkgs, Pkg.mark4removal)
351
352 def mark4moving(self, wannabepkgs):
353 self.__mark4something(wannabepkgs, Pkg.mark4moving)
354
355
356 # Internal functions below
b36df4e6
ER
357 def __arch_stringify(self, list):
358 ret = []
359 # XXX: is dist line in any config?
360 dist = 'ac'
361 for arch in list:
362 ret.append(dist + '-' + arch)
363 return ' '.join(ret)
14085d11 364
d9b3388c
MM
365 def __loadpkgnames(self):
366 def checkfiletype(name):
367 if name[-13:]=='.src.rpm.info':
368 return True
369 else:
370 return False
8911f226
ER
371 list = filter(checkfiletype, os.listdir(self.basedir+'/SRPMS/.metadata'))
372 self.pkgnames = map((lambda x: x[:-13]), list)
d9b3388c 373
14085d11 374 def __mark4something(self, wannabepkgs, markfunction):
098f4a50 375 def chopoffextension(pkg):
f6dff636
ER
376 found = pkg.find('.src.rpm')
377 if found == -1:
098f4a50
MM
378 return pkg
379 else:
380 return pkg[:found]
f6dff636 381
098f4a50 382 for wannabepkg in wannabepkgs:
f6dff636 383 pkgname = chopoffextension(wannabepkg)
098f4a50
MM
384 if pkgname in self.pkgnames:
385 if not pkgname in self.loadedpkgs.keys():
386 self.loadedpkgs[pkgname]=Pkg(pkgname, self)
14085d11 387 markfunction(self.loadedpkgs[pkgname])
098f4a50 388 else:
492b6398 389 perror('%s not found in source tree' % pkgname)
098f4a50 390 bailoutonerror()
14085d11 391
67b5bf38 392 def __checkbuild(self, marked):
8911f226
ER
393 """
394 Checks queue file if all arches are built
395
396 Reads config.builderqueue to grab the info
397 """
398 f = urllib.urlopen(config.builderqueue)
399 requests = {}
400 reid = re.compile(r'^.*id=(.*) pri.*$')
401 regb = re.compile(r'^group:.*$|builders:.*$', re.M)
098f4a50 402 for i in re.findall(regb, f.read()):
8911f226
ER
403 if i[0] == 'g':
404 id = reid.sub(r'\1', i)
405 requests[id] = ""
098f4a50 406 elif i[0]=='b':
8911f226 407 requests[id] = requests[id] + i
098f4a50 408 f.close()
8911f226 409
6bc6286e 410 for pkg in marked:
098f4a50
MM
411 for bid in pkg.build.keys():
412 if requests.has_key(bid) and not requests[bid].find('?') == -1:
85f3481a 413 pkg.error("(buildid %s) building not finished" % bid)
098f4a50 414
67b5bf38 415 def __checkarchs(self, dsttree, marked):
8911f226
ER
416 """
417 Checks marked pkgs it is built on all archs.
418 """
67b5bf38 419 for pkg in marked:
4802753d 420 if len(pkg.files.keys()) <= 1:
85f3481a 421 pkg.error('has only src.rpm built')
4802753d 422 continue
8911f226
ER
423 otherpkgnames = self.__find_other_pkgs(pkg, dsttree)
424
425 # check if we're not removing some archs
426 if otherpkgnames:
427 curarchs = []
428 missingarchs = []
796b7867
MM
429 for somepkg in otherpkgnames:
430 curarchs.extend(Pkg(somepkg, dsttree).files.keys())
431 for arch in curarchs:
432 if arch not in pkg.files.keys():
433 missingarchs.append(arch)
434 if missingarchs:
b36df4e6 435 pkg.error('moving would remove archs: %s' % self.__arch_stringify(missingarchs))
8911f226
ER
436 else:
437 # warn if a package isn't built for all archs
438 if (config.separate_noarch and 'noarch' in pkg.files.keys() and len(pkg.files.keys()) == 2):
492b6398 439 continue
8911f226
ER
440 elif len(pkg.files.keys()) != len(config.ftp_archs) + 1:
441 missingarchs = []
492b6398
MM
442 for arch in config.ftp_archs:
443 if arch not in pkg.files.keys():
444 missingarchs.append(arch)
b36df4e6 445 pkg.warning('not built for archs: %s' % self.__arch_stringify(missingarchs))
098f4a50 446
332719f8 447 def __checkduplicates(self, marked):
d97ec2d6
AM
448 """
449 Checks if marked packages contain duplicate packages (with different versions)
450 """
451 for pkg in marked:
452 olderpkgnames = self.__find_older_pkgs(pkg)
453 for i in olderpkgnames:
454 pkg.error('duplicate package: %s' % i)
455
213a164a 456 def __rmolderfromsrc(self, test = False):
14085d11 457 for pkg in self.marked4moving:
8911f226 458 olderpkgnames = self.__find_older_pkgs(pkg)
098f4a50 459 for i in olderpkgnames:
a0b52be0 460 Pkg(i, self).remove(test)
098f4a50 461
213a164a 462 def __rmotherfromdst(self, dsttree, test = False, archivetree = None):
14085d11 463 for pkg in self.marked4moving:
8911f226 464 pkgnames = self.__find_other_pkgs(pkg, dsttree)
098f4a50 465 for i in pkgnames:
213a164a
ER
466 if archivetree == None:
467 Pkg(i, dsttree).remove(test)
468 else:
469 Pkg(i, dsttree).move(archivetree, test = test)
098f4a50 470
d9b3388c 471 # Used more than once filter functions
d9b3388c 472 def __find_other_pkgs(self, pkg, tree):
8911f226
ER
473 escapedpkgname = pkg.name.replace('.', '\.').replace('+', '\+')
474 ziewre = re.compile(escapedpkgname + '-[^-]*-[^-]*$')
d9b3388c 475 def filter_other_pkgs(x):
85f3481a 476 if ziewre.match(x) and not x == pkg.nvr:
d9b3388c
MM
477 return True
478 else:
479 return False
480 return filter(filter_other_pkgs, tree.pkgnames)
481
482 def __find_older_pkgs(self, pkg):
483 def filter_older_pkgs(x):
8911f226 484 c = x.split('-')
ce470bd9
MM
485 rc = rpm.labelCompare(('0', pkg.version, pkg.release),
486 ('0', c[-2], c[-1]))
487 if rc == 1: # pkg > x
d9b3388c 488 return True
d9b3388c
MM
489 else:
490 return False
491 return filter(filter_older_pkgs, self.__find_other_pkgs(pkg, self))
492
aeb928ae
ER
493 def __checksigns(self, tree, pkgs, test = False):
494 """
495 Checks if pkgs in tree are all signed.
496
497 in case of test = true, error flag is set for unsigned packages
498 """
499 if not tree.treename in config.signed_trees:
500 return
501
502 for pkg in pkgs:
503 unsigned = 0
504 for file in pkg.rpmfiles():
505 if not is_signed(file):
f6dff636 506 unsigned += 1
aeb928ae
ER
507
508 if unsigned != 0:
509 if test == True:
510 if not quietmode:
511 pkg.warning('%d files not signed' % unsigned)
512 else:
513 pkg.error('%d files not signed' % unsigned)
f6dff636
ER
514
515 def __checkforobsoletes(self, tree, pkgs, test = False):
516 """
517 Checks queue file if package obsoletes something in destination tree and suggest for removal.
518
519 Only NAME tag is compared, i.e virtual packages do not get reported.
520
521 """
522 if test != True:
523 return
524
525 def findbyname(name):
526 def x(nvr):
527 return '-'.join(nvr.split('-')[:-2]) == name
528 return filter(x, tree.pkgnames)
529
530 for pkg in pkgs:
531 obsoletes = pkg.obsoletes()
532 if not obsoletes:
533 continue
534
535 for pn, setlist in obsoletes.items():
536 for item in setlist:
537 p = findbyname(item)
538 if p:
9e047859 539 pkg.warning('obsoletes %s (via %s) in dest tree, perhaps you want rmpkg' % (p,pn))
8643d2cd
ER
540
541 def __checkforrelease(self, tree, pkgs, test = False):
542 """
543 Checks queue file if package release is non integer.
544
545 """
546 if test != True:
547 return
548
549 for pkg in pkgs:
550 if not pkg.is_release():
551 pkg.warning('non-integer release: %s' % pkg.release)
This page took 0.149712 seconds and 4 git commands to generate.