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