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