Package backend :: Package satellite_tools :: Module xmlSource
[hide private]
[frames] | no frames]

Source Code for Module backend.satellite_tools.xmlSource

   1  # 
   2  # Decoding data from XML streams 
   3  # 
   4  # Copyright (c) 2008--2018 Red Hat, Inc. 
   5  # 
   6  # This software is licensed to you under the GNU General Public License, 
   7  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
   8  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
   9  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  10  # along with this software; if not, see 
  11  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
  12  # 
  13  # Red Hat trademarks are not licensed under GPLv2. No permission is 
  14  # granted to use or replicate Red Hat trademarks that are incorporated 
  15  # in this software or its documentation. 
  16  # 
  17   
  18  import sys 
  19  import re 
  20  from xml.sax import make_parser, SAXParseException, ContentHandler, \ 
  21      ErrorHandler 
  22   
  23  from spacewalk.common import usix 
  24  from spacewalk.common import rhnFlags 
  25  from spacewalk.common.rhnLog import log_debug 
  26  from spacewalk.common.rhnConfig import CFG 
  27  from spacewalk.common.rhnTB import Traceback 
  28  from spacewalk.server.importlib import importLib, backendLib 
  29   
  30  RHEL234_REGEX = re.compile("rhel-[^-]*-[aew]s-(4|3|2.1)") 
31 32 # Terminology used throughout this file: 33 # Item: an atomic entity from the database's perspective. 34 # A channel, or a package, or an erratum is an item. 35 # Container: a list of items 36 # We work under the assumption everything on the second level (i.e. a child 37 # of the root element) is a container. 38 39 # The way the parser works: get a handler with getHandler() and call process() 40 # with an XML stream. 41 42 # Our parser exceptions 43 44 45 -class ParseException(Exception):
46 47 """general parser exception (generated at this level). 48 """ 49 pass
50
51 52 -class _EndContainerEvent(Exception):
53
54 - def __init__(self, container):
55 Exception.__init__(self) 56 self.container = container
57
58 59 -class IncompatibleVersionError(ParseException):
60
61 - def __init__(self, stream_version, parser_version, *args):
62 ParseException.__init__(self, *args) 63 self.stream_version = stream_version 64 self.parser_version = parser_version
65
66 # XML parser exception wrappers 67 # Exposed functionality for the next three include: 68 # getColumnNumber(), getLineNumber(), and _msg (or just str(e)) 69 70 71 -class RecoverableParseException(SAXParseException, Exception):
72 73 """exception wrapper for a critical, but possibly recoverable, XML parser 74 error. 75 """ 76 pass
77
78 79 -class FatalParseException(SAXParseException, Exception):
80 81 """exception wrapper for a critical XML parser error. 82 """ 83 pass
84
85 # XML Node 86 87 88 -class Node:
89
90 - def __init__(self, name, attributes=None, subelements=None):
91 self.name = name 92 if attributes is None: 93 attributes = {} 94 if subelements is None: 95 subelements = [] 96 self.attributes = attributes 97 self.subelements = subelements
98
99 - def addSubelement(self, e):
100 self.subelements.append(e)
101
102 - def __repr__(self):
103 return "[<Node element: name=%s>]" % self.name
104
105 106 # Base class we use as a SAX parsing handler 107 -class BaseDispatchHandler(ContentHandler, ErrorHandler):
108 109 """ Base class we use as a SAX parsing handler 110 111 We expect the meaningful data to be on the third level. 112 The root element defines what the export contains, while the collection 113 element defines what this collection contains 114 """ 115 rootElement = None # non-static 116 __stream = None 117 container_dispatch = {} 118
119 - def __init__(self):
120 ContentHandler.__init__(self) 121 self.rootAttributes = None 122 self.__parser = make_parser() 123 # Init the parser's handlers 124 self.restoreParser() 125 # No container at this time 126 self.__container = None 127 # Reset all the containers, to make sure previous runs don't leave 128 # garbage data 129 for container in self.container_dispatch.values(): 130 container.reset()
131
132 - def restoreParser(self):
133 # Restore the parser's handlers to self 134 self.__parser.setContentHandler(self) 135 self.__parser.setErrorHandler(self)
136 137 @staticmethod
138 - def setStream(stream):
140 141 # Starts processing the data from the XML stream
142 - def process(self, stream=None):
143 log_debug(6) 144 if stream is not None: 145 self.setStream(stream) 146 try: 147 self.__parser.parse(self.__stream) 148 except (KeyboardInterrupt, SystemExit): 149 raise 150 except Exception: # pylint: disable=E0012, W0703 151 Traceback(ostream=sys.stderr, with_locals=1) 152 if stream is not None: 153 stream.close() 154 sys.exit(1)
155
156 - def reset(self):
157 self.close() 158 # Re-init 159 self.__init__()
160
161 - def close(self):
162 # WARNING: better call this function when you're done, or you'll end 163 # up with a circular reference 164 self.__parser = None
165
166 - def clear(self):
167 # clear out the current container's parse batch; start afresh 168 if self.__container: 169 try: 170 self.__container.batch = [] 171 except (KeyboardInterrupt, SystemExit): 172 raise 173 except Exception: 174 e = sys.exc_info()[1] 175 log_debug(-1, 'ERROR (odd) upon container.batch=[] cleanup: %s' % e) 176 raise
177 178 # Interface with containers
179 - def set_container(self, obj):
180 if not hasattr(obj, "container_name"): 181 raise Exception("%s not a container type" % type(obj)) 182 183 # reset the container (to clean up garbage from previous parses) 184 obj.reset() 185 self.container_dispatch[obj.container_name] = obj
186
187 - def get_container(self, name):
188 if name not in self.container_dispatch: 189 # Return a dummy container 190 c = ContainerHandler() 191 c.container_name = name 192 return c 193 194 return self.container_dispatch[name]
195
196 - def has_container(self, name):
197 return (name in self.container_dispatch)
198 199 # Overwrite the functions required by SAX
200 - def setDocumentLocator(self, locator):
201 ContentHandler.setDocumentLocator(self, locator)
202 203 # def startDocument(self): 204 205 # def endDocument(self): 206
207 - def startElement(self, name, attrs):
208 log_debug(6, name) 209 utf8_attrs = _dict_to_utf8(attrs) 210 if self.rootAttributes is None: 211 # First time around 212 if self.rootElement != name: 213 raise Exception("Mismatching elements; root='%s', " 214 "received='%s'" % (self.rootElement, name)) 215 self.rootAttributes = utf8_attrs 216 self._check_version() 217 return 218 219 if self.__container is None: 220 # This means it's parsing a container element 221 self.__container = self.get_container(name) 222 223 self.__container.startElement(name, utf8_attrs)
224
225 - def characters(self, content):
226 if self.__container: 227 self.__container.characters(_stringify(content))
228
229 - def endElement(self, name):
230 log_debug(6, name) 231 if self.__container is None: 232 # End of the root attribute 233 # We know now the tag stack is empty 234 self.rootAttributes = None 235 return 236 237 try: 238 self.__container.endElement(name) 239 except _EndContainerEvent: 240 self.__container = None
241 242 #___Error handling methods___ 243 244 # pylint: disable=W0212,W0710
245 - def error(self, exception):
246 """Handle a recoverable error. 247 """ 248 log_debug(-1, "ERROR (RECOVERABLE): parse error encountered - line: %s, col: %s, msg: %s" 249 % (exception.getLineNumber(), exception.getColumnNumber(), exception._msg)) 250 raise RecoverableParseException(exception._msg, exception, exception._locator)
251
252 - def fatalError(self, exception):
253 """Handle a non-recoverable error. 254 """ 255 log_debug(-1, "ERROR (FATAL): parse error encountered - line: %s, col: %s, msg: %s" 256 % (exception.getLineNumber(), exception.getColumnNumber(), exception._msg)) 257 raise FatalParseException(exception._msg, exception, exception._locator)
258
259 - def warning(self, exception):
260 """Handle a warning. 261 """ 262 log_debug(-1, "ERROR (WARNING): parse error encountered - line: %s, col: %s, msg: %s" 263 % (exception.getLineNumber(), exception.getColumnNumber(), exception._msg))
264 265 # To be overridden in subclasses
266 - def _check_version(self):
267 pass
268
269 # Particular case: a satellite handler 270 271 272 -class SatelliteDispatchHandler(BaseDispatchHandler):
273 rootElement = 'rhn-satellite' 274 # this is the oldest version of channel dump we support 275 version = "3.0" 276 277 # Historical log 278 # * Version 2.2 2004-03-02 279 # arch types introduced in all the arch dumps 280 # * Version 2.3 2004-09-13 281 # added short package dumps per channel 282 # * Version 3.0 2005-01-13 283 # required major version change for channel family merging (#136525) 284
285 - def _check_version(self):
286 # Check the version 287 version = self.rootAttributes.get("version") 288 # Entitlement/certificate generation 289 generation = self.rootAttributes.get("generation") 290 rhnFlags.set("stream-generation", generation) 291 if not version: 292 version = "0" 293 stream_version = list(map(int, version.split('.'))) 294 allowed_version = list(map(int, self.version.split("."))) 295 if (stream_version[0] != allowed_version[0] or 296 stream_version[1] < allowed_version[1]): 297 raise IncompatibleVersionError(version, self.version, 298 "Incompatible stream version %s; code supports %s" % ( 299 version, self.version))
300
301 # Element handler 302 303 304 -class BaseItem:
305 item_name = None 306 item_class = object 307 tagMap = {} 308
309 - def __init__(self):
310 pass
311
312 - def populate(self, attributes, elements):
313 item = self.item_class() 314 # Populate the item from the attribute data structure 315 self.populateFromAttributes(item, attributes) 316 # Populate the item from sub-elements 317 self.populateFromElements(item, elements) 318 return item
319
320 - def populateFromAttributes(self, obj, sourceDict):
321 # Populates dict with items from sourceDict 322 for key, value in sourceDict.items(): 323 if key not in self.tagMap: 324 if key not in obj: 325 # Unsupported key 326 continue 327 else: 328 # Have to map this key 329 key = self.tagMap[key] 330 331 # Finally, update the key 332 obj[key] = _normalizeAttribute(obj.attributeTypes.get(key), value)
333
334 - def populateFromElements(self, obj, elements):
335 # Populates obj with `elements' as subelements 336 keys = list(obj.keys()) 337 keys_len = len(keys) 338 for element in elements: 339 if _is_string(element): 340 if keys_len != 1: 341 if not element.strip(): 342 # White space around an element - skip 343 continue 344 # Ambiguity: don't know which attribute to initialize 345 raise Exception("Ambiguity %s" % keys) 346 # Init the only attribute we know of 347 obj[keys[0]] = element 348 continue 349 name = element.name 350 if name not in obj and name not in self.tagMap: 351 # Unsupported key 352 continue 353 if name in self.tagMap: 354 # Have to map this element 355 name = self.tagMap[name] 356 value = _normalizeSubelements(obj.attributeTypes.get(name), 357 element.subelements) 358 obj[name] = value
359
360 361 -def _is_string(obj):
362 if isinstance(obj, usix.StringType): 363 return 1 364 if isinstance(obj, usix.UnicodeType): 365 return 1 366 return 0
367
368 369 -def _stringify(data):
370 # Accelerate the most common cases 371 if isinstance(data, usix.StringType): 372 return data 373 elif isinstance(data, usix.UnicodeType): 374 return data.encode('UTF8') 375 return str(data)
376
377 378 -def _dict_to_utf8(d):
379 # Convert the dictionary to have non-unocide key-value pairs 380 ret = {} 381 for k, v in d.items(): 382 if isinstance(k, usix.UnicodeType): 383 k = k.encode('UTF8') 384 if isinstance(v, usix.UnicodeType): 385 v = v.encode('UTF8') 386 ret[k] = v 387 return ret
388 389 390 __itemDispatcher = {}
391 392 393 -def addItem(classobj):
394 __itemDispatcher[classobj.item_name] = classobj
395
396 397 -def _createItem(element):
398 # Creates an Item object from the specified element 399 if element.name not in __itemDispatcher: 400 # No item processor 401 return None 402 item = __itemDispatcher[element.name]() 403 return item.populate(element.attributes, element.subelements)
404
405 # 406 # ITEMS: 407 # 408 409 410 -class BaseArchItem(BaseItem):
411 pass
412
413 414 -class ServerArchItem(BaseArchItem):
415 item_name = 'rhn-server-arch' 416 item_class = importLib.ServerArch
417 addItem(ServerArchItem)
418 419 420 -class PackageArchItem(BaseArchItem):
421 item_name = 'rhn-package-arch' 422 item_class = importLib.PackageArch
423 addItem(PackageArchItem)
424 425 426 -class ChannelArchItem(BaseArchItem):
427 item_name = 'rhn-channel-arch' 428 item_class = importLib.ChannelArch
429 addItem(ChannelArchItem)
430 431 432 -class CPUArchItem(BaseItem):
433 item_name = 'rhn-cpu-arch' 434 item_class = importLib.CPUArch
435 addItem(CPUArchItem)
436 437 438 -class ServerPackageArchCompatItem(BaseItem):
439 item_name = 'rhn-server-package-arch-compat' 440 item_class = importLib.ServerPackageArchCompat
441 addItem(ServerPackageArchCompatItem)
442 443 444 -class ServerChannelArchCompatItem(BaseItem):
445 item_name = 'rhn-server-channel-arch-compat' 446 item_class = importLib.ServerChannelArchCompat
447 addItem(ServerChannelArchCompatItem)
448 449 450 -class ChannelPackageArchCompatItem(BaseItem):
451 item_name = 'rhn-channel-package-arch-compat' 452 item_class = importLib.ChannelPackageArchCompat
453 addItem(ChannelPackageArchCompatItem)
454 455 456 -class ServerGroupServerArchCompatItem(BaseItem):
457 item_name = 'rhn-server-group-server-arch-compat' 458 item_class = importLib.ServerGroupServerArchCompat
459 addItem(ServerGroupServerArchCompatItem)
460 461 462 -class ChannelFamilyItem(BaseItem):
463 item_name = 'rhn-channel-family' 464 item_class = importLib.ChannelFamily 465 tagMap = { 466 'id': 'channel-family-id', 467 # max_members is no longer populated from the xml dump, but from the 468 # satellite cert 469 'rhn-channel-family-name': 'name', 470 'rhn-channel-family-product-url': 'product_url', 471 'channel-labels': 'channels', 472 }
473 addItem(ChannelFamilyItem)
474 475 476 -class ChannelItem(BaseItem):
477 item_name = 'rhn-channel' 478 item_class = importLib.Channel 479 tagMap = { 480 'channel-id': 'string_channel_id', 481 'org-id': 'org_id', 482 'rhn-channel-parent-channel': 'parent_channel', 483 'rhn-channel-families': 'families', 484 'channel-arch': 'channel_arch', 485 'rhn-channel-basedir': 'basedir', 486 'rhn-channel-name': 'name', 487 'rhn-channel-summary': 'summary', 488 'rhn-channel-description': 'description', 489 'rhn-channel-last-modified': 'last_modified', 490 'rhn-dists': 'dists', 491 'rhn-release': 'release', 492 'channel-errata': 'errata', 493 'kickstartable-trees': 'kickstartable_trees', 494 'rhn-channel-errata': 'errata_timestamps', 495 'source-packages': 'source_packages', 496 'rhn-channel-gpg-key-url': 'gpg_key_url', 497 'rhn-channel-product-name': 'product_name', 498 'rhn-channel-product-version': 'product_version', 499 'rhn-channel-product-beta': 'product_beta', 500 'rhn-channel-receiving-updates': 'receiving_updates', 501 'rhn-channel-checksum-type': 'checksum_type', 502 'rhn-channel-comps-last-modified': 'comps_last_modified', 503 'rhn-channel-modules-last-modified': 'modules_last_modified', 504 'sharing': 'channel_access', 505 'rhn-channel-trusted-orgs': 'trust_list', 506 } 507
508 - def populateFromElements(self, obj, elements):
509 # bz 808516, to retain compatibility with Satellite <= 5.3 we 510 # need to assume sha1 checksum type unless we explicitly see 511 # 'rhn-null' in the xml 512 checksum_type_really_null = False 513 for element in elements: 514 if (not _is_string(element) 515 and element.name == 'rhn-channel-checksum-type'): 516 for subelement in element.subelements: 517 if (not _is_string(subelement) 518 and subelement.name == 'rhn-null'): 519 checksum_type_really_null = True 520 521 BaseItem.populateFromElements(self, obj, elements) 522 523 if obj['checksum_type'] == 'sha': 524 obj['checksum_type'] = 'sha1' 525 if not obj['checksum_type'] and not checksum_type_really_null: 526 obj['checksum_type'] = 'sha1' 527 528 # if importing from an old export that does not know about 529 # channel_access, use the default 530 if not obj['channel_access']: 531 obj['channel_access'] = 'private' 532 533 # if using versions of rhel that doesn't use yum, set 534 # checksum_type to None 535 if (RHEL234_REGEX.match(obj['label']) 536 or (obj['parent_channel'] 537 and RHEL234_REGEX.match(obj['parent_channel']))): 538 obj['checksum_type'] = None
539 540 addItem(ChannelItem)
541 542 543 -class ChannelTrustItem(BaseItem):
544 item_name = 'rhn-channel-trusted-org' 545 item_class = importLib.ChannelTrust 546 tagMap = { 547 'org-id': 'org_trust_id', 548 }
549 addItem(ChannelTrustItem)
550 551 552 -class OrgTrustItem(BaseItem):
553 item_name = 'rhn-org-trust' 554 item_class = importLib.OrgTrust 555 tagMap = { 556 'org-id': 'org_id', 557 }
558 addItem(OrgTrustItem)
559 560 561 -class OrgItem(BaseItem):
562 item_name = 'rhn-org' 563 item_class = importLib.Org 564 tagMap = { 565 'id': 'id', 566 'name': 'name', 567 'rhn-org-trusts': 'org_trust_ids', 568 }
569 addItem(OrgItem)
570 571 572 -class BaseChecksummedItem(BaseItem):
573
574 - def populate(self, attributes, elements):
575 item = BaseItem.populate(self, attributes, elements) 576 item['checksums'] = {} 577 if 'md5sum' in item: 578 # xml dumps < 3.6 (aka pre-sha256) 579 item['checksums']['md5'] = item['md5sum'] 580 del(item['md5sum']) 581 if 'checksum_list' in item and item['checksum_list']: 582 for csum in item['checksum_list']: 583 item['checksums'][csum['type']] = csum['value'] 584 del(item['checksum_list']) 585 for ctype in CFG.CHECKSUM_PRIORITY_LIST: 586 if ctype in item['checksums']: 587 item['checksum_type'] = ctype 588 item['checksum'] = item['checksums'][ctype] 589 break 590 return item
591 addItem(BaseChecksummedItem)
592 593 594 -class IncompletePackageItem(BaseChecksummedItem):
595 item_name = 'rhn-package-short' 596 item_class = importLib.IncompletePackage 597 tagMap = { 598 'id': 'package_id', 599 'package-size': 'package_size', 600 'last-modified': 'last_modified', 601 'package-arch': 'arch', 602 'org-id': 'org_id', 603 'checksums': 'checksum_list', 604 }
605 addItem(IncompletePackageItem)
606 607 608 -class ChecksumItem(BaseItem):
609 item_name = 'checksum' 610 item_class = importLib.Checksum 611 tagMap = { 612 'checksum-type': 'type', 613 'checksum-value': 'value', 614 }
615 addItem(ChecksumItem)
616 617 618 -class PackageItem(IncompletePackageItem):
619 item_name = 'rhn-package' 620 item_class = importLib.Package 621 tagMap = { 622 # Stuff coming through as attributes 623 'package-group': 'package_group', 624 'rpm-version': 'rpm_version', 625 'payload-size': 'payload_size', 626 'build-host': 'build_host', 627 'build-time': 'build_time', 628 'source-rpm': 'source_rpm', 629 'payload-format': 'payload_format', 630 # Stuff coming through as subelements 631 'rhn-package-summary': 'summary', 632 'rhn-package-description': 'description', 633 'rhn-package-vendor': 'vendor', 634 'rhn-package-copyright': 'license', 635 'rhn-package-header-sig': 'header_sig', 636 # These are duplicated as attributes, should go away eventually 637 'rhn-package-package-group': 'package_group', 638 'rhn-package-rpm-version': 'rpm_version', 639 'rhn-package-payload-size': 'payload_size', 640 'rhn-package-header-start': 'header_start', 641 'rhn-package-header-end': 'header_end', 642 'rhn-package-build-host': 'build_host', 643 'rhn-package-build-time': 'build_time', 644 'rhn-package-source-rpm': 'source_rpm', 645 'rhn-package-payload-format': 'payload_format', 646 'rhn-package-cookie': 'cookie', 647 # 648 'rhn-package-files': 'files', 649 'rhn-package-requires': 'requires', 650 'rhn-package-provides': 'provides', 651 'rhn-package-conflicts': 'conflicts', 652 'rhn-package-obsoletes': 'obsoletes', 653 'rhn-package-recommends': 'recommends', 654 'rhn-package-suggests': 'suggests', 655 'rhn-package-supplements': 'supplements', 656 'rhn-package-enhances': 'enhances', 657 'rhn-package-changelog': 'changelog', 658 } 659 tagMap.update(IncompletePackageItem.tagMap) 660
661 - def populate(self, attributes, elements):
662 item = IncompletePackageItem.populate(self, attributes, elements) 663 # find out "primary" checksum 664 # pylint: disable=bad-option-value,unsubscriptable-object,unsupported-assignment-operation 665 have_filedigests = len([1 for i in item['requires'] if i['name'] == 'rpmlib(FileDigests)']) 666 if not have_filedigests: 667 item['checksum_type'] = 'md5' 668 item['checksum'] = item['checksums']['md5'] 669 return item
670 addItem(PackageItem)
671 672 673 -class IncompleteSourcePackageItem(BaseItem):
674 item_name = 'source-package' 675 item_class = importLib.IncompleteSourcePackage 676 tagMap = { 677 'last-modified': 'last_modified', 678 'source-rpm': 'source_rpm', 679 }
680 addItem(IncompleteSourcePackageItem)
681 682 683 -class SourcePackageItem(BaseItem):
684 item_name = 'rhn-source-package' 685 item_class = importLib.SourcePackage 686 tagMap = { 687 'id': 'package_id', 688 'source-rpm': 'source_rpm', 689 'package-group': 'package_group', 690 'rpm-version': 'rpm_version', 691 'payload-size': 'payload_size', 692 'build-host': 'build_host', 693 'build-time': 'build_time', 694 'package-size': 'package_size', 695 'last-modified': 'last_modified', 696 }
697 addItem(SourcePackageItem)
698 699 700 -class ChangelogItem(BaseItem):
701 item_name = 'rhn-package-changelog-entry' 702 item_class = importLib.ChangeLog 703 tagMap = { 704 'rhn-package-changelog-entry-name': 'name', 705 'rhn-package-changelog-entry-text': 'text', 706 'rhn-package-changelog-entry-time': 'time', 707 }
708 addItem(ChangelogItem)
709 710 711 -class DependencyItem(BaseItem):
712 713 """virtual class - common settings for dependency items""" 714 item_class = importLib.Dependency 715 tagMap = { 716 'sense': 'flags', 717 }
718
719 720 -class ProvidesItem(DependencyItem):
721 item_name = 'rhn-package-provides-entry'
722 addItem(ProvidesItem)
723 724 725 -class RequiresItem(DependencyItem):
726 item_name = 'rhn-package-requires-entry'
727 addItem(RequiresItem)
728 729 730 -class ConflictsItem(DependencyItem):
731 item_name = 'rhn-package-conflicts-entry'
732 addItem(ConflictsItem)
733 734 735 -class ObsoletesItem(DependencyItem):
736 item_name = 'rhn-package-obsoletes-entry'
737 addItem(ObsoletesItem)
738 739 740 -class RecommendsItem(DependencyItem):
741 item_name = 'rhn-package-recommends-entry'
742 addItem(RecommendsItem)
743 744 745 -class SuggestsItem(DependencyItem):
746 item_name = 'rhn-package-suggests-entry'
747 addItem(SuggestsItem)
748 749 750 -class SupplementsItem(DependencyItem):
751 item_name = 'rhn-package-supplements-entry'
752 addItem(SupplementsItem)
753 754 755 -class EnhancesItem(DependencyItem):
756 item_name = 'rhn-package-enhances-entry'
757 addItem(EnhancesItem)
758 759 760 -class FileItem(BaseChecksummedItem):
761 item_name = 'rhn-package-file' 762 item_class = importLib.File 763 tagMap = { 764 'checksum-type': 'checksum_type', 765 } 766
767 - def populate(self, attributes, elements):
768 if 'md5' in attributes and 'checksum-type' not in attributes: 769 attributes['checksum-type'] = 'md5' 770 attributes['checksum'] = attributes['md5'] 771 item = BaseChecksummedItem.populate(self, attributes, elements) 772 return item
773 addItem(FileItem)
774 775 776 -class DistItem(BaseItem):
777 item_name = 'rhn-dist' 778 item_class = importLib.DistChannelMap 779 tagMap = { 780 'channel-arch': 'channel_arch', 781 }
782 addItem(DistItem)
783 784 785 -class ChannelErratumItem(BaseItem):
786 item_name = 'erratum' 787 item_class = importLib.ChannelErratum 788 tagMap = { 789 'last-modified': 'last_modified', 790 'advisory-name': 'advisory_name', 791 }
792 addItem(ChannelErratumItem)
793 794 795 -class ReleaseItem(BaseItem):
796 item_name = 'rhn-release' 797 item_class = importLib.ReleaseChannelMap 798 tagMap = { 799 'channel-arch': 'channel_arch' 800 }
801 addItem(ReleaseItem)
802 803 804 -class BugItem(BaseItem):
805 item_name = 'rhn-erratum-bug' 806 item_class = importLib.Bug 807 tagMap = { 808 'rhn-erratum-bug-id': 'bug_id', 809 'rhn-erratum-bug-summary': 'summary', 810 'rhn-erratum-bug-href': 'href', 811 }
812 addItem(BugItem)
813 814 815 -class KeywordItem(BaseItem):
816 item_name = 'rhn-erratum-keyword' 817 item_class = importLib.Keyword 818 tagMap = { 819 }
820 addItem(KeywordItem)
821 822 823 -class ErratumItem(BaseItem):
824 item_name = 'rhn-erratum' 825 item_class = importLib.Erratum 826 tagMap = { 827 'id': 'erratum_id', 828 'org-id': 'org_id', 829 'rhn-erratum-advisory-name': 'advisory_name', 830 'rhn-erratum-advisory-rel': 'advisory_rel', 831 'rhn-erratum-advisory-type': 'advisory_type', 832 'rhn-erratum-product': 'product', 833 'rhn-erratum-description': 'description', 834 'rhn-erratum-synopsis': 'synopsis', 835 'rhn-erratum-topic': 'topic', 836 'rhn-erratum-solution': 'solution', 837 'rhn-erratum-issue-date': 'issue_date', 838 'rhn-erratum-update-date': 'update_date', 839 'rhn-erratum-notes': 'notes', 840 'rhn-erratum-org-id': 'org_id', 841 'rhn-erratum-refers-to': 'refers_to', 842 'rhn-erratum-channels': 'channels', 843 'rhn-erratum-keywords': 'keywords', 844 'rhn-erratum-checksums': 'checksums', 845 'rhn-erratum-bugs': 'bugs', 846 'rhn-erratum-cve': 'cve', 847 'rhn-erratum-last-modified': 'last_modified', 848 'rhn-erratum-files': 'files', 849 'rhn-erratum-errata-from': 'errata_from', 850 'rhn-erratum-severity': 'severity_id', 851 'cve-names': 'cve', 852 }
853 addItem(ErratumItem)
854 855 856 -class ErrorItem(BaseItem):
857 item_name = 'rhn-error' 858 item_class = importLib.Error
859 addItem(ErrorItem)
860 861 862 -class ErrataFileItem(BaseChecksummedItem):
863 item_name = 'rhn-erratum-file' 864 item_class = importLib.ErrataFile 865 tagMap = { 866 'type': 'file_type', 867 'channels': 'channel_list', 868 # Specific to XML 869 'package': 'package', 870 'source-package': 'source-package', 871 'checksum-type': 'checksum_type', 872 }
873 addItem(ErrataFileItem)
874 875 876 -class ProductNamesItem(BaseItem):
877 item_name = 'rhn-product-name' 878 item_class = importLib.ProductName
879 addItem(ProductNamesItem)
880 881 882 -class KickstartableTreeItem(BaseItem):
883 item_name = 'rhn-kickstartable-tree' 884 item_class = importLib.KickstartableTree 885 tagMap = { 886 'rhn-kickstart-files': 'files', 887 'base-path': 'base_path', 888 'boot-image': 'boot_image', 889 'kstree-type-label': 'kstree_type_label', 890 'install-type-label': 'install_type_label', 891 'kstree-type-name': 'kstree_type_name', 892 'install-type-name': 'install_type_name', 893 'last-modified': 'last_modified', 894 }
895 addItem(KickstartableTreeItem)
896 897 898 -class KickstartFileItem(BaseChecksummedItem):
899 item_name = 'rhn-kickstart-file' 900 item_class = importLib.KickstartFile 901 tagMap = { 902 'relative-path': 'relative_path', 903 'file-size': 'file_size', 904 'last-modified': 'last_modified', 905 'checksums': 'checksum_list', 906 }
907 addItem(KickstartFileItem)
908 909 # 910 # Container handler and containers: 911 # 912 913 914 -class ContainerHandler:
915 container_name = None 916
917 - def __init__(self):
918 # The tag stack; each item is an array [element, attributes] 919 self.tagStack = [] 920 # The object stack; each item is an array 921 # [element, attributes, content] 922 self.objStack = [] 923 # Collects the elements in a batch 924 self.batch = []
925
926 - def reset(self):
927 # Make sure the batch is preserved 928 batch = self.batch 929 # Re-init the object: cleans up the stacks and such 930 self.__init__() 931 # And restore the batch 932 self.batch = batch
933
934 - def startElement(self, element, attrs):
935 # log_debug(6, element) --duplicate logging. 936 if not self.tagStack and element != self.container_name: 937 # Strange; this element is called to parse stuff when it's not 938 # supposed to 939 raise Exception('This object should not have been used') 940 self.tagStack.append(Node(element, attrs)) 941 self.objStack.append([])
942
943 - def characters(self, data):
944 log_debug(6, data) 945 if data == '': 946 # Nothing to do 947 return 948 # If the thing in front is a string, append to it 949 lastObj = self.objStack[-1] 950 if lastObj and _is_string(lastObj[-1]): 951 lastObj[-1] = '%s%s' % (lastObj[-1], data) 952 else: 953 lastObj.append(data)
954
955 - def endElement(self, element):
956 # log_debug(6, element) --duplicate logging. 957 tagobj = self.tagStack[-1] 958 # Remove the previous tag 959 del self.tagStack[-1] 960 # Decode the tag object 961 name = tagobj.name 962 if name != element: 963 raise ParseException( 964 "incorrect XML data: closing tag %s, opening tag %s" % ( 965 element, name)) 966 # Append the content of the object to the tag object 967 for obj in self.objStack[-1]: 968 tagobj.addSubelement(obj) 969 970 # Remove the subelements from the stack 971 del self.objStack[-1] 972 973 if not self.objStack: 974 # End element for this container 975 self.endContainerCallback() 976 raise _EndContainerEvent(tagobj) 977 978 # Regular element; append the current object as a subelement to the 979 # previous object 980 self.objStack[-1].append(tagobj) 981 if len(self.tagStack) == 1: 982 # Finished parsing an item; let the parent know 983 self.endItemCallback()
984
985 - def getLastItem(self):
986 return self.objStack[-1][-1]
987
988 - def clearLastItem(self):
989 del self.objStack[-1][-1]
990
991 - def endItemCallback(self):
992 # Grab the latest object we've parsed 993 obj = self.getLastItem() 994 # And remove it since we don't need it 995 self.clearLastItem() 996 # Instantiate the object 997 item = _createItem(obj) 998 999 if item is None: 1000 # Nothing to do with this object 1001 return 1002 1003 if 'error' in item: 1004 # Special case errors 1005 log_debug(0, 'XML parser error: found "rhn-error" item: %s' % 1006 item['error']) 1007 raise ParseException(item['error']) 1008 1009 self.postprocessItem(item) 1010 # Add it to the items list 1011 self.batch.append(item)
1012
1013 - def endContainerCallback(self):
1014 pass
1015
1016 - def postprocessItem(self, item):
1017 # Do nothing 1018 pass
1019
1020 1021 -def _normalizeSubelements(objtype, subelements):
1022 # pylint: disable=R0911 1023 # Deal with simple cases first 1024 if objtype is None: 1025 # Don't know how to handle it 1026 return _stringify(subelements) 1027 1028 if not subelements: 1029 # No subelements available 1030 if isinstance(objtype, usix.ListType): 1031 # Expect a list of things - return the empty list 1032 return [] 1033 # Expected a scalar type 1034 return None 1035 1036 # We do have subelements 1037 # Extract all the non-string subelements 1038 _s = [] 1039 _strings_only = 1 1040 for subel in subelements: 1041 if _is_string(subel) and not subel.strip(): 1042 # Ignore it for now 1043 continue 1044 _s.append(subel) 1045 if not _is_string(subel): 1046 _strings_only = 0 1047 1048 if _strings_only: 1049 # Multiple strings - contactenate into one 1050 subelements = [''.join(subelements)] 1051 else: 1052 # Ignore whitespaces around elements 1053 subelements = _s 1054 1055 if not isinstance(objtype, usix.ListType): 1056 if len(subelements) > 1: 1057 raise Exception("Expected a scalar, got back a list") 1058 subelement = subelements[0] 1059 # NULL? 1060 if isinstance(subelement, Node): 1061 if subelement.name == 'rhn-null': 1062 return None 1063 raise Exception("Expected a scalar, got back an element '%s'" % subelement.name) 1064 1065 if objtype is usix.StringType: 1066 return _stringify(subelement) 1067 1068 if objtype is usix.IntType: 1069 if subelement == '': 1070 # Treat it as NULL 1071 return None 1072 return int(subelement) 1073 1074 if objtype is importLib.DateType: 1075 return _normalizeDateType(subelement) 1076 raise Exception("Unhandled type %s for subelement %s" % (objtype, 1077 subelement)) 1078 1079 # Expecting a list of things 1080 expectedType = objtype[0] 1081 if expectedType is usix.StringType: 1082 # List of strings 1083 return list(map(_stringify, subelements)) 1084 1085 if expectedType is usix.IntType: 1086 # list of ints 1087 return list(map(int, subelements)) 1088 1089 if expectedType is importLib.DateType: 1090 return list(map(_normalizeDateType, subelements)) 1091 1092 # A subelement 1093 result = [] 1094 for subelement in subelements: 1095 item = _createItem(subelement) 1096 if item is None: 1097 # Item processor not found 1098 continue 1099 if not isinstance(item, expectedType): 1100 raise Exception("Expected type %s, got back %s %s" % (expectedType, 1101 type(item), item)) 1102 result.append(item) 1103 1104 return result
1105
1106 1107 -def _normalizeAttribute(objtype, attribute):
1108 # Deal with simple cases first 1109 if (objtype is None) or (objtype is usix.StringType): 1110 # (Don't know how to handle it) or (Expecting a scalar) 1111 return attribute 1112 elif objtype is usix.IntType: 1113 if attribute == '' or attribute == 'None': 1114 # Treat it as NULL 1115 return None 1116 else: 1117 return int(attribute) 1118 elif objtype is importLib.DateType: 1119 return _normalizeDateType(attribute) 1120 elif isinstance(objtype, usix.ListType): 1121 # List type - split stuff 1122 return attribute.split() 1123 else: 1124 raise Exception("Unhandled attribute data type %s" % objtype)
1125
1126 1127 -def _normalizeDateType(value):
1128 try: 1129 value = int(value) 1130 except ValueError: 1131 # string 1132 return value 1133 # Timestamp 1134 return backendLib.localtime(value)
1135
1136 1137 # 1138 # Containers: 1139 # 1140 # XXX: we'll need an ErrorContainer eventually 1141 # (we do not handle <rhn-error> properly if it is 1142 # a "root" element). 1143 # class ErrorContainer(ContainerHandler): 1144 # container_name = 'rhn-error' 1145 # def endContainerCallback(self): 1146 # lastObj = self.getLastItem() 1147 # raise ParseException(lastObj) 1148 1149 1150 -class ChannelFamilyContainer(ContainerHandler):
1151 container_name = 'rhn-channel-families'
1152
1153 1154 -class ChannelContainer(ContainerHandler):
1155 container_name = 'rhn-channels'
1156
1157 1158 -class IncompletePackageContainer(ContainerHandler):
1159 container_name = 'rhn-packages-short' 1160
1161 - def postprocessItem(self, item):
1162 channels = [] 1163 for channel in item['channels'] or []: 1164 c = importLib.Channel() 1165 c['label'] = channel 1166 channels.append(c) 1167 item['channels'] = channels
1168
1169 1170 -class PackageContainer(IncompletePackageContainer):
1171 1172 """Inherits from IncompletePackageContainer, since we need to postprocess the 1173 channel information 1174 """ 1175 container_name = 'rhn-packages'
1176
1177 1178 -class SourcePackageContainer(ContainerHandler):
1179 container_name = 'rhn-source-packages'
1180
1181 1182 -class ErrataContainer(IncompletePackageContainer):
1183 container_name = 'rhn-errata'
1184
1185 1186 -class ServerArchContainer(ContainerHandler):
1187 container_name = 'rhn-server-arches'
1188
1189 1190 -class PackageArchContainer(ContainerHandler):
1191 container_name = 'rhn-package-arches'
1192
1193 1194 -class ChannelArchContainer(ContainerHandler):
1195 container_name = 'rhn-channel-arches'
1196
1197 1198 -class CPUArchContainer(ContainerHandler):
1199 container_name = 'rhn-cpu-arches'
1200
1201 1202 -class ServerPackageArchCompatContainer(ContainerHandler):
1203 container_name = 'rhn-server-package-arch-compatibility-map'
1204
1205 1206 -class ServerChannelArchCompatContainer(ContainerHandler):
1207 container_name = 'rhn-server-channel-arch-compatibility-map'
1208
1209 1210 -class ChannelPackageArchCompatContainer(ContainerHandler):
1211 container_name = 'rhn-channel-package-arch-compatibility-map'
1212
1213 1214 -class ServerGroupServerArchCompatContainer(ContainerHandler):
1215 container_name = 'rhn-server-group-server-arch-compatibility-map'
1216
1217 1218 -class ProductNamesContainer(ContainerHandler):
1219 container_name = 'rhn-product-names'
1220
1221 1222 -class KickstartableTreesContainer(ContainerHandler):
1223 container_name = 'rhn-kickstartable-trees'
1224
1225 1226 -class OrgContainer(ContainerHandler):
1227 container_name = 'rhn-orgs'
1228