Ejemplo n.º 1
0
 def _open_metadata_file_handle(self):
     """
     Open the metadata file handle, creating any missing parent directories.
     If the file already exists, this will overwrite it.
     """
     super(UpdateinfoXMLFileContext, self)._open_metadata_file_handle()
     self.xml_generator = XMLWriter(self.metadata_file_handle, short_empty_elements=True)
Ejemplo n.º 2
0
 def _open_metadata_file_handle(self):
     """
     Open the metadata file handle, creating any missing parent directories.
     If the file already exists, this will overwrite it.
     """
     super(UpdateinfoXMLFileContext, self)._open_metadata_file_handle()
     self.xml_generator = XMLWriter(self.metadata_file_handle, short_empty_elements=True)
Ejemplo n.º 3
0
 def test_short_empty_elements_false(self):
     """
     Test that XML is generated correctly and a short form of empty element is not used.
     """
     fake_file = StringIO()
     self.xml_generator = XMLWriter(fake_file)
     self._calls_to_test_xml_generator()
     generated_xml = fake_file.getvalue()
     fake_file.close()
     expected_xml = '<?xml version="1.0" encoding="utf-8"?>\n' \
                    '<!DOCTYPE string here>\n' \
                    '<outer_tag1>\n' \
                    '  <inner_tag1>content in utf-8</inner_tag1>\n' \
                    '  <inner_tag2 attr1="value1">content in unicode</inner_tag2>\n' \
                    '  <inner_tag3 attr2="value2"></inner_tag3>\n' \
                    '</outer_tag1>\n' \
                    '<outer_tag2></outer_tag2>\n'
     self.assertEqual(generated_xml, expected_xml)
Ejemplo n.º 4
0
class UpdateinfoXMLFileContext(FastForwardXmlFileContext):
    def __init__(self,
                 working_dir,
                 checksum_type=None,
                 conduit=None,
                 updateinfo_checksum_type=None):
        """
        Creates and writes updateinfo XML data.

        :param working_dir: The working directory for the request
        :type working_dir:  basestring
        :param checksum_type: The type of checksum to be used
        :type checksum_type:  basestring
        :param conduit: A conduit to use
        :type conduit:  pulp.plugins.conduits.repo_publish.RepoPublishConduit
        :param updateinfo_checksum_type: The type of checksum to be used in the package list
        :type updateinfo_checksum_type:  basestring
        """
        metadata_file_path = os.path.join(working_dir, REPO_DATA_DIR_NAME,
                                          UPDATE_INFO_XML_FILE_NAME)
        self.conduit = conduit
        root_tag, search_tag = 'updates', None
        super(UpdateinfoXMLFileContext,
              self).__init__(metadata_file_path,
                             root_tag,
                             search_tag,
                             checksum_type=checksum_type)
        self.updateinfo_checksum_type = updateinfo_checksum_type
        self.optional_errata_fields = ('title', 'release', 'rights',
                                       'solution', 'severity', 'summary',
                                       'pushcount')
        self.mandatory_errata_fields = ('description', )

    def _open_metadata_file_handle(self):
        """
        Open the metadata file handle, creating any missing parent directories.
        If the file already exists, this will overwrite it.
        """
        super(UpdateinfoXMLFileContext, self)._open_metadata_file_handle()
        self.xml_generator = XMLWriter(self.metadata_file_handle,
                                       short_empty_elements=True)

    def _get_package_checksum_tuple(self, package):
        """
        Decide which checksum to publish for the given package in the erratum package list.
        If updateinfo_checksum_type is requested explicitly, the checksum of this type will be
        published.
        If no checksum_type is requested, the checksum of the distributor checksum type
        will be published, if available. Otherwise the longest one will be chosen.

        Handle two possible ways of specifying the checksum in the erratum package list:
        - in the `sum` package field as a list of alternating checksum types and values,
          e.g. ['type1', 'checksum1', 'type2', 'checksum2']
        - in the `type` and `sums` package fields. It is only the case when the erratum was uploaded
          via pulp-admin. Only one type of the checksum could be specified this way.

        :param package: package from the erratum package list
        :type  package: dict
        :return: checksum type and value to publish. An empty tuple is returned if there is
                 no checksum available.
        :rtype: tuple
        :raises PulpCodedException: if updateinfo_checksum_type is not available
        """
        package_checksum_tuple = ()
        dist_checksum_type = self.checksum_type
        package_checksums = package.get('sum') or []
        if package.get('type'):
            package_checksums += [package['type'], package.get('sums')]

        for checksum_type in (self.updateinfo_checksum_type,
                              dist_checksum_type):
            try:
                checksum_index = package_checksums.index(checksum_type) + 1
            except (ValueError, IndexError):
                # raise exception if updateinfo_checksum_type is unavailable
                if self.updateinfo_checksum_type and \
                   checksum_type == self.updateinfo_checksum_type:
                    raise PulpCodedException(
                        error_codes.RPM1012,
                        checksumtype=self.updateinfo_checksum_type)
                continue
            else:
                checksum_value = package_checksums[checksum_index]
                package_checksum_tuple = (checksum_type, checksum_value)
                break
        else:
            if package_checksums:
                # choose the longest(the best?) checksum available
                checksum_value = max(package_checksums[1::2], key=len)
                checksum_type_index = package_checksums.index(
                    checksum_value) - 1
                checksum_type = package_checksums[checksum_type_index]
                package_checksum_tuple = (checksum_type, checksum_value)

        return package_checksum_tuple

    def add_unit_metadata(self, item, filtered_pkglist):
        """
        Write the XML representation of erratum_unit to self.metadata_file_handle
        (updateinfo.xml.gx).

        :param item: The erratum unit that should be written to updateinfo.xml.
        :type  item: pulp_rpm.plugins.db.models.Errata
        :param filtered_pkglist: The pkglist containing unique non-empty collections
                                with packages which are present in repo.
        :type filtered_pkglist: dict
        """
        erratum_unit = item
        update_attributes = {
            'status': erratum_unit.status,
            'type': erratum_unit.type,
            'version': erratum_unit.version,
            'from': erratum_unit.errata_from or ''
        }
        self.xml_generator.startElement('update', update_attributes)
        self.xml_generator.completeElement('id', {}, erratum_unit.errata_id)
        issued_attributes = {'date': erratum_unit.issued}
        self.xml_generator.completeElement('issued', issued_attributes, '')

        if erratum_unit.reboot_suggested:
            self.xml_generator.completeElement('reboot_suggested', {}, 'True')

        for element in self.optional_errata_fields:
            element_value = getattr(erratum_unit, element)
            if not element_value:
                continue
            self.xml_generator.completeElement(element, {},
                                               unicode(element_value))

        for element in self.mandatory_errata_fields:
            element_value = getattr(erratum_unit, element)
            element_value = '' if element_value is None else element_value
            self.xml_generator.completeElement(element, {},
                                               unicode(element_value))

        updated = erratum_unit.updated
        if updated:
            updated_attributes = {'date': updated}
            self.xml_generator.completeElement('updated', updated_attributes,
                                               '')

        self.xml_generator.startElement('references')
        for reference in erratum_unit.references:
            reference_attributes = {
                'id': reference['id'] or '',
                'title': reference['title'] or '',
                'type': reference['type'],
                'href': reference['href']
            }
            self.xml_generator.completeElement('reference',
                                               reference_attributes, '')
        self.xml_generator.endElement('references')

        self.xml_generator.startElement('pkglist')
        collection_attributes = {}
        short = filtered_pkglist.get('short')
        if short is not None:
            collection_attributes['short'] = short
        self.xml_generator.startElement('collection', collection_attributes)
        self.xml_generator.completeElement('name', {},
                                           filtered_pkglist['name'])

        for package in filtered_pkglist['packages']:
            package_attributes = {
                'name': package['name'],
                'version': package['version'],
                'release': package['release'],
                'epoch': package['epoch'] or '0',
                'arch': package['arch'],
                'src': package.get('src', '') or ''
            }
            self.xml_generator.startElement('package', package_attributes)
            self.xml_generator.completeElement('filename', {},
                                               package['filename'])

            package_checksum_tuple = self._get_package_checksum_tuple(package)
            if package_checksum_tuple:
                checksum_type, checksum_value = package_checksum_tuple
                sum_attributes = {'type': checksum_type}
                self.xml_generator.completeElement('sum', sum_attributes,
                                                   checksum_value)

            if package.get('reboot_suggested'):
                self.xml_generator.completeElement('reboot_suggested', {},
                                                   'True')
            if package.get('relogin_suggested'):
                self.xml_generator.completeElement(
                    'relogin_suggested', {}, package['relogin_suggested'])
            if package.get('restart_suggested'):
                self.xml_generator.completeElement(
                    'restart_suggested', {}, package['restart_suggested'])

            self.xml_generator.endElement('package')

        self.xml_generator.endElement('collection')
        self.xml_generator.endElement('pkglist')
        self.xml_generator.endElement('update')
Ejemplo n.º 5
0
class UpdateinfoXMLFileContext(FastForwardXmlFileContext):
    def __init__(self, working_dir, nevra_in_repo, checksum_type=None, conduit=None,
                 updateinfo_checksum_type=None):
        """
        Creates and writes updateinfo XML data.

        :param working_dir: The working directory for the request
        :type working_dir:  basestring
        :param nevra_in_repo: The nevra of all rpms in the repo.
        :type nevra_in_repo:  set of models.NEVRA objects
        :param checksum_type: The type of checksum to be used
        :type checksum_type:  basestring
        :param conduit: A conduit to use
        :type conduit:  pulp.plugins.conduits.repo_publish.RepoPublishConduit
        :param updateinfo_checksum_type: The type of checksum to be used in the package list
        :type updateinfo_checksum_type:  basestring
        """
        self.nevra_in_repo = nevra_in_repo
        metadata_file_path = os.path.join(working_dir, REPO_DATA_DIR_NAME,
                                          UPDATE_INFO_XML_FILE_NAME)
        self.conduit = conduit
        root_tag, search_tag = 'updates', None
        super(UpdateinfoXMLFileContext, self).__init__(
            metadata_file_path, root_tag, search_tag, checksum_type=checksum_type)
        self.updateinfo_checksum_type = updateinfo_checksum_type
        self.optional_errata_fields = ('title', 'release', 'rights', 'solution', 'severity',
                                       'summary', 'pushcount')
        self.mandatory_errata_fields = ('description',)

    def _open_metadata_file_handle(self):
        """
        Open the metadata file handle, creating any missing parent directories.
        If the file already exists, this will overwrite it.
        """
        super(UpdateinfoXMLFileContext, self)._open_metadata_file_handle()
        self.xml_generator = XMLWriter(self.metadata_file_handle, short_empty_elements=True)

    def _get_repo_unit_nevra(self, erratum_unit):
        """
        Return a list of NEVRA dicts for units in a single repo referenced by the given errata.

        Pulp errata units combine the known packages from all synced repos. Given an errata unit
        and a repo, return a list of NEVRA dicts that can be used to filter out packages not
        linked to that repo when generating a repo's updateinfo XML file.

        :param erratum_unit: The erratum unit that should be written to updateinfo.xml.
        :type erratum_unit: pulp_rpm.plugins.db.models.Errata

        :return: a list of NEVRA dicts for units in a single repo referenced by the given errata
        :rtype: list
        """
        nevra_in_repo_and_pkglist = []
        for pkglist in erratum_unit.pkglist:
            for pkg in pkglist['packages']:
                pkg_nevra = models.NEVRA(name=pkg['name'], epoch=pkg['epoch'] or '0',
                                         version=pkg['version'], release=pkg['release'],
                                         arch=pkg['arch'])
                if pkg_nevra in self.nevra_in_repo:
                    pkg_dict = {
                        'name': pkg_nevra.name,
                        'epoch': pkg_nevra.epoch,
                        'version': pkg_nevra.version,
                        'release': pkg_nevra.release,
                        'arch': pkg_nevra.arch,
                    }
                    nevra_in_repo_and_pkglist.append(pkg_dict)
        return nevra_in_repo_and_pkglist

    def _get_package_checksum_tuple(self, package):
        """
        Decide which checksum to publish for the given package in the erratum package list.
        If updateinfo_checksum_type is requested explicitly, the checksum of this type will be
        published.
        If no checksum_type is requested, the checksum of the distributor checksum type
        will be published, if available. Otherwise the longest one will be chosen.

        Handle two possible ways of specifying the checksum in the erratum package list:
        - in the `sum` package field as a list of alternating checksum types and values,
          e.g. ['type1', 'checksum1', 'type2', 'checksum2']
        - in the `type` and `sums` package fields. It is only the case when the erratum was uploaded
          via pulp-admin. Only one type of the checksum could be specified this way.

        :param package: package from the erratum package list
        :type  package: dict
        :return: checksum type and value to publish. An empty tuple is returned if there is
                 no checksum available.
        :rtype: tuple
        :raises PulpCodedException: if updateinfo_checksum_type is not available
        """
        package_checksum_tuple = ()
        dist_checksum_type = self.checksum_type
        package_checksums = package.get('sum') or []
        if package.get('type'):
            package_checksums += [package['type'], package.get('sums')]

        for checksum_type in (self.updateinfo_checksum_type, dist_checksum_type):
            try:
                checksum_index = package_checksums.index(checksum_type) + 1
            except (ValueError, IndexError):
                # raise exception if updateinfo_checksum_type is unavailable
                if self.updateinfo_checksum_type and \
                   checksum_type == self.updateinfo_checksum_type:
                    raise PulpCodedException(error_codes.RPM1012,
                                             checksumtype=self.updateinfo_checksum_type)
                continue
            else:
                checksum_value = package_checksums[checksum_index]
                package_checksum_tuple = (checksum_type, checksum_value)
                break
        else:
            if package_checksums:
                # choose the longest(the best?) checksum available
                checksum_value = max(package_checksums[1::2], key=len)
                checksum_type_index = package_checksums.index(checksum_value) - 1
                checksum_type = package_checksums[checksum_type_index]
                package_checksum_tuple = (checksum_type, checksum_value)

        return package_checksum_tuple

    def add_unit_metadata(self, item):
        """
        Write the XML representation of erratum_unit to self.metadata_file_handle
        (updateinfo.xml.gx).

        :param item: The erratum unit that should be written to updateinfo.xml.
        :type  item: pulp_rpm.plugins.db.models.Errata
        """
        erratum_unit = item
        update_attributes = {'status': erratum_unit.status,
                             'type': erratum_unit.type,
                             'version': erratum_unit.version,
                             'from': erratum_unit.errata_from or ''}
        self.xml_generator.startElement('update', update_attributes)
        self.xml_generator.completeElement('id', {}, erratum_unit.errata_id)
        issued_attributes = {'date': erratum_unit.issued}
        self.xml_generator.completeElement('issued', issued_attributes, '')

        if erratum_unit.reboot_suggested:
            self.xml_generator.completeElement('reboot_suggested', {}, 'True')

        for element in self.optional_errata_fields:
            element_value = getattr(erratum_unit, element)
            if not element_value:
                continue
            self.xml_generator.completeElement(element, {}, unicode(element_value))

        for element in self.mandatory_errata_fields:
            element_value = getattr(erratum_unit, element)
            element_value = '' if element_value is None else element_value
            self.xml_generator.completeElement(element, {}, unicode(element_value))

        updated = erratum_unit.updated
        if updated:
            updated_attributes = {'date': updated}
            self.xml_generator.completeElement('updated', updated_attributes, '')

        self.xml_generator.startElement('references')
        for reference in erratum_unit.references:
            reference_attributes = {'id': reference['id'] or '',
                                    'title': reference['title'] or '',
                                    'type': reference['type'],
                                    'href': reference['href']}
            self.xml_generator.completeElement('reference', reference_attributes, '')
        self.xml_generator.endElement('references')

        # If we can pull a repo_id off the conduit, use that to generate repo-specific nevra
        if self.conduit and hasattr(self.conduit, 'repo_id'):
            repo_unit_nevra = self._get_repo_unit_nevra(erratum_unit)
        else:
            repo_unit_nevra = None

        seen_pkglists = set()
        for pkglist in erratum_unit.pkglist:
            packages = tuple(sorted(p['filename'] for p in pkglist['packages']))
            if packages in seen_pkglists:
                continue
            seen_pkglists.add(packages)

            self.xml_generator.startElement('pkglist')
            collection_attributes = {}
            short = pkglist.get('short')
            if short is not None:
                collection_attributes['short'] = short
            self.xml_generator.startElement('collection', collection_attributes)
            self.xml_generator.completeElement('name', {}, pkglist['name'])

            for package in pkglist['packages']:
                package_attributes = {'name': package['name'],
                                      'version': package['version'],
                                      'release': package['release'],
                                      'epoch': package['epoch'] or '0',
                                      'arch': package['arch'],
                                      'src': package.get('src', '') or ''}
                if repo_unit_nevra is not None:
                    # If repo_unit_nevra can be used for comparison, take the src attr out of a
                    # copy of this package's attrs to get a nevra dict for comparison
                    package_nevra = package_attributes.copy()
                    del(package_nevra['src'])
                    if package_nevra not in repo_unit_nevra:
                        # current package not in the specified repo, don't add it to the output
                        continue

                self.xml_generator.startElement('package', package_attributes)
                self.xml_generator.completeElement('filename', {}, package['filename'])

                package_checksum_tuple = self._get_package_checksum_tuple(package)
                if package_checksum_tuple:
                    checksum_type, checksum_value = package_checksum_tuple
                    sum_attributes = {'type': checksum_type}
                    self.xml_generator.completeElement('sum', sum_attributes, checksum_value)

                if package.get('reboot_suggested'):
                    self.xml_generator.completeElement('reboot_suggested', {}, 'True')
                self.xml_generator.endElement('package')

            self.xml_generator.endElement('collection')
            self.xml_generator.endElement('pkglist')
        self.xml_generator.endElement('update')
Ejemplo n.º 6
0
class UpdateinfoXMLFileContext(XmlFileContext):
    def __init__(self, working_dir, checksum_type=None, conduit=None):
        metadata_file_path = os.path.join(working_dir, REPO_DATA_DIR_NAME,
                                          UPDATE_INFO_XML_FILE_NAME)
        self.conduit = conduit
        super(UpdateinfoXMLFileContext,
              self).__init__(metadata_file_path,
                             'updates',
                             checksum_type=checksum_type)
        self.optional_errata_fields = ('title', 'release', 'rights',
                                       'solution', 'severity', 'summary',
                                       'pushcount')
        self.mandatory_errata_fields = ('description', )

    def _open_metadata_file_handle(self):
        """
        Open the metadata file handle, creating any missing parent directories.
        If the file already exists, this will overwrite it.
        """
        super(XmlFileContext, self)._open_metadata_file_handle()
        self.xml_generator = XMLWriter(self.metadata_file_handle,
                                       short_empty_elements=True)

    def _repo_unit_nevra(self, erratum_unit, repo_id):
        """
        Return a list of NEVRA dicts for units in a single repo referenced by the given errata.

        Pulp errata units combine the known packages from all synced repos. Given an errata unit
        and a repo, return a list of NEVRA dicts that can be used to filter out packages not
        linked to that repo when generating a repo's updateinfo XML file. While returning that
        list of NEVRA dicts is the main goal, doing so quickly and without running out of memory
        is what makes this a little bit tricky.

        Build up a super-fancy query to get the unit ids for all NEVRA seen in these errata
        check repo/unit associations for this errata to limit the packages in the published
        updateinfo to the units in the repo being currently published.

        :param erratum_unit: The erratum unit that should be written to updateinfo.xml.
        :type erratum_unit: pulp_rpm.plugins.db.models.Errata
        :param repo_id: The repo_id of a pulp repository in which to find units
        :type repo_id: str
        :return: a list of NEVRA dicts for units in a single repo referenced by the given errata
        :rtype: list
        """
        nevra_fields = ('name', 'epoch', 'version', 'release', 'arch')
        nevra_q = mongoengine.Q()
        for pkglist in erratum_unit.pkglist:
            for pkg in pkglist['packages']:
                pkg_nevra = dict((field, pkg[field]) for field in nevra_fields)
                nevra_q |= mongoengine.Q(**pkg_nevra)
        # Aim the super-fancy query at mongo to get the units that this errata refers to
        # The scaler method on the end returns a list of tuples to try to save some memory
        # and also cut down on mongoengine model instance hydration costs.
        nevra_units = models.RPM.objects.filter(nevra_q).scalar(
            'id', *nevra_fields)

        # Split up the nevra unit entries into a mapping of the unit id to its nevra fields
        nevra_unit_map = dict(
            (nevra_unit[0], nevra_unit[1:]) for nevra_unit in nevra_units)

        # Get all of the unit ids from this errata that are associated with the current repo.
        # Cast this as a set for speedier lookups when iterating of the nevra unit map.
        repo_unit_ids = set(
            RepositoryContentUnit.objects.filter(
                unit_id__in=nevra_unit_map.keys(),
                repo_id=repo_id).scalar('unit_id'))

        # Finally(!), intersect the repo unit ids with the unit nevra ids to
        # create a list of nevra dicts that can be easily compared to the
        # errata package nevra and exclude unrelated packages
        repo_unit_nevra = []
        for nevra_unit_id, nevra_field_values in nevra_unit_map.items():
            # based on the args to scalar when nevra_units was created:
            if nevra_unit_id in repo_unit_ids:
                repo_unit_nevra.append(
                    dict(zip(nevra_fields, nevra_field_values)))

        return repo_unit_nevra

    def add_unit_metadata(self, item):
        """
        Write the XML representation of erratum_unit to self.metadata_file_handle
        (updateinfo.xml.gx).

        :param item: The erratum unit that should be written to updateinfo.xml.
        :type  item: pulp_rpm.plugins.db.models.Errata
        """
        erratum_unit = item
        update_attributes = {
            'status': erratum_unit.status,
            'type': erratum_unit.type,
            'version': erratum_unit.version,
            'from': erratum_unit.errata_from or ''
        }
        self.xml_generator.startElement('update', update_attributes)
        self.xml_generator.completeElement('id', {}, erratum_unit.errata_id)
        issued_attributes = {'date': erratum_unit.issued}
        self.xml_generator.completeElement('issued', issued_attributes, '')

        if erratum_unit.reboot_suggested is None:
            erratum_unit.reboot_suggested = False
        reboot_suggested_str = str(erratum_unit.reboot_suggested)
        self.xml_generator.completeElement('reboot_suggested', {},
                                           reboot_suggested_str)

        for element in self.optional_errata_fields:
            element_value = getattr(erratum_unit, element)
            if not element_value:
                continue
            self.xml_generator.completeElement(element, {},
                                               unicode(element_value))

        for element in self.mandatory_errata_fields:
            element_value = getattr(erratum_unit, element)
            element_value = '' if element_value is None else element_value
            self.xml_generator.completeElement(element, {},
                                               unicode(element_value))

        updated = erratum_unit.updated
        if updated:
            updated_attributes = {'date': updated}
            self.xml_generator.completeElement('updated', updated_attributes,
                                               '')

        self.xml_generator.startElement('references')
        for reference in erratum_unit.references:
            reference_attributes = {
                'id': reference['id'] or '',
                'title': reference['title'] or '',
                'type': reference['type'],
                'href': reference['href']
            }
            self.xml_generator.completeElement('reference',
                                               reference_attributes, '')
        self.xml_generator.endElement('references')

        # If we can pull a repo_id off the conduit, use that to generate repo-specific nevra
        if self.conduit and hasattr(self.conduit, 'repo_id'):
            repo_unit_nevra = self._repo_unit_nevra(erratum_unit,
                                                    self.conduit.repo_id)
        else:
            repo_unit_nevra = None

        seen_pkglists = set()
        for pkglist in erratum_unit.pkglist:
            packages = tuple(sorted(p['filename']
                                    for p in pkglist['packages']))
            if packages in seen_pkglists:
                continue
            seen_pkglists.add(packages)

            self.xml_generator.startElement('pkglist')
            collection_attributes = {}
            short = pkglist.get('short')
            if short is not None:
                collection_attributes['short'] = short
            self.xml_generator.startElement('collection',
                                            collection_attributes)
            self.xml_generator.completeElement('name', {}, pkglist['name'])

            for package in pkglist['packages']:
                package_attributes = {
                    'name': package['name'],
                    'version': package['version'],
                    'release': package['release'],
                    'epoch': package['epoch'] or '0',
                    'arch': package['arch'],
                    'src': package.get('src', '') or ''
                }
                if repo_unit_nevra is not None:
                    # If repo_unit_nevra can be used for comparison, take the src attr out of a
                    # copy of this package's attrs to get a nevra dict for comparison
                    package_nevra = package_attributes.copy()
                    del (package_nevra['src'])
                    if package_nevra not in repo_unit_nevra:
                        # current package not in the specified repo, don't add it to the output
                        continue

                self.xml_generator.startElement('package', package_attributes)
                self.xml_generator.completeElement('filename', {},
                                                   package['filename'])

                checksum_tuple = package.get('sum', None)
                if checksum_tuple is not None:
                    checksum_type, checksum_value = checksum_tuple
                    sum_attributes = {'type': checksum_type}
                    self.xml_generator.completeElement('sum', sum_attributes,
                                                       checksum_value)

                reboot_suggested_str = str(
                    package.get('reboot_suggested', False))
                self.xml_generator.completeElement('reboot_suggested', {},
                                                   reboot_suggested_str)
                self.xml_generator.endElement('package')

            self.xml_generator.endElement('collection')
            self.xml_generator.endElement('pkglist')
        self.xml_generator.endElement('update')
Ejemplo n.º 7
0
    def test_utf8_writes(self):
        """
        Test that utf-8 non-ascii characters are handled without complaint.
        """
        xml_generator = XMLWriter(StringIO())

        tag = u'𝅘𝅥𝅮'
        xml_generator.startDocument()
        xml_generator.writeDoctype('<!DOCTYPE string here>')
        xml_generator.startElement(tag, {u'𝆑': u'𝆒'})
        xml_generator.completeElement(u'inner_tag2 Ώ', {u'attr1 ỳ': u'value1 ỳ'}, u'ỳ')
        xml_generator.endElement(tag)
        xml_generator.endDocument()
Ejemplo n.º 8
0
class UpdateinfoXMLFileContext(FastForwardXmlFileContext):
    def __init__(self, working_dir, checksum_type=None, conduit=None,
                 updateinfo_checksum_type=None):
        """
        Creates and writes updateinfo XML data.

        :param working_dir: The working directory for the request
        :type working_dir:  basestring
        :param checksum_type: The type of checksum to be used
        :type checksum_type:  basestring
        :param conduit: A conduit to use
        :type conduit:  pulp.plugins.conduits.repo_publish.RepoPublishConduit
        :param updateinfo_checksum_type: The type of checksum to be used in the package list
        :type updateinfo_checksum_type:  basestring
        """
        metadata_file_path = os.path.join(working_dir, REPO_DATA_DIR_NAME,
                                          UPDATE_INFO_XML_FILE_NAME)
        self.conduit = conduit
        root_tag, search_tag = 'updates', None
        super(UpdateinfoXMLFileContext, self).__init__(
            metadata_file_path, root_tag, search_tag, checksum_type=checksum_type)
        self.updateinfo_checksum_type = updateinfo_checksum_type
        self.optional_errata_fields = ('title', 'release', 'rights', 'solution', 'severity',
                                       'summary', 'pushcount')
        self.mandatory_errata_fields = ('description',)

    def _open_metadata_file_handle(self):
        """
        Open the metadata file handle, creating any missing parent directories.
        If the file already exists, this will overwrite it.
        """
        super(UpdateinfoXMLFileContext, self)._open_metadata_file_handle()
        self.xml_generator = XMLWriter(self.metadata_file_handle, short_empty_elements=True)

    def _get_package_checksum_tuple(self, package):
        """
        Decide which checksum to publish for the given package in the erratum package list.
        If updateinfo_checksum_type is requested explicitly, the checksum of this type will be
        published.
        If no checksum_type is requested, the checksum of the distributor checksum type
        will be published, if available. Otherwise the longest one will be chosen.

        Handle two possible ways of specifying the checksum in the erratum package list:
        - in the `sum` package field as a list of alternating checksum types and values,
          e.g. ['type1', 'checksum1', 'type2', 'checksum2']
        - in the `type` and `sums` package fields. It is only the case when the erratum was uploaded
          via pulp-admin. Only one type of the checksum could be specified this way.

        :param package: package from the erratum package list
        :type  package: dict
        :return: checksum type and value to publish. An empty tuple is returned if there is
                 no checksum available.
        :rtype: tuple
        :raises PulpCodedException: if updateinfo_checksum_type is not available
        """
        package_checksum_tuple = ()
        dist_checksum_type = self.checksum_type
        package_checksums = package.get('sum') or []
        if package.get('type'):
            package_checksums += [package['type'], package.get('sums')]

        for checksum_type in (self.updateinfo_checksum_type, dist_checksum_type):
            try:
                checksum_index = package_checksums.index(checksum_type) + 1
            except (ValueError, IndexError):
                # raise exception if updateinfo_checksum_type is unavailable
                if self.updateinfo_checksum_type and \
                   checksum_type == self.updateinfo_checksum_type:
                    raise PulpCodedException(error_codes.RPM1012,
                                             checksumtype=self.updateinfo_checksum_type)
                continue
            else:
                checksum_value = package_checksums[checksum_index]
                package_checksum_tuple = (checksum_type, checksum_value)
                break
        else:
            if package_checksums:
                # choose the longest(the best?) checksum available
                checksum_value = max(package_checksums[1::2], key=len)
                checksum_type_index = package_checksums.index(checksum_value) - 1
                checksum_type = package_checksums[checksum_type_index]
                package_checksum_tuple = (checksum_type, checksum_value)

        return package_checksum_tuple

    def add_unit_metadata(self, item, filtered_pkglist):
        """
        Write the XML representation of erratum_unit to self.metadata_file_handle
        (updateinfo.xml.gx).

        :param item: The erratum unit that should be written to updateinfo.xml.
        :type  item: pulp_rpm.plugins.db.models.Errata
        :param filtered_pkglist: The pkglist containing unique non-empty collections
                                with packages which are present in repo.
        :type filtered_pkglist: dict
        """
        erratum_unit = item
        update_attributes = {'status': erratum_unit.status,
                             'type': erratum_unit.type,
                             'version': erratum_unit.version,
                             'from': erratum_unit.errata_from or ''}
        self.xml_generator.startElement('update', update_attributes)
        self.xml_generator.completeElement('id', {}, erratum_unit.errata_id)
        issued_attributes = {'date': erratum_unit.issued}
        self.xml_generator.completeElement('issued', issued_attributes, '')

        if erratum_unit.reboot_suggested:
            self.xml_generator.completeElement('reboot_suggested', {}, 'True')

        for element in self.optional_errata_fields:
            element_value = getattr(erratum_unit, element)
            if not element_value:
                continue
            self.xml_generator.completeElement(element, {}, unicode(element_value))

        for element in self.mandatory_errata_fields:
            element_value = getattr(erratum_unit, element)
            element_value = '' if element_value is None else element_value
            self.xml_generator.completeElement(element, {}, unicode(element_value))

        updated = erratum_unit.updated
        if updated:
            updated_attributes = {'date': updated}
            self.xml_generator.completeElement('updated', updated_attributes, '')

        self.xml_generator.startElement('references')
        for reference in erratum_unit.references:
            reference_attributes = {'id': reference['id'] or '',
                                    'title': reference['title'] or '',
                                    'type': reference['type'],
                                    'href': reference['href']}
            self.xml_generator.completeElement('reference', reference_attributes, '')
        self.xml_generator.endElement('references')

        self.xml_generator.startElement('pkglist')
        collection_attributes = {}
        short = filtered_pkglist.get('short')
        if short is not None:
            collection_attributes['short'] = short
        self.xml_generator.startElement('collection', collection_attributes)
        self.xml_generator.completeElement('name', {}, filtered_pkglist['name'])

        for package in filtered_pkglist['packages']:
            package_attributes = {'name': package['name'],
                                  'version': package['version'],
                                  'release': package['release'],
                                  'epoch': package['epoch'] or '0',
                                  'arch': package['arch'],
                                  'src': package.get('src', '') or ''}
            self.xml_generator.startElement('package', package_attributes)
            self.xml_generator.completeElement('filename', {}, package['filename'])

            package_checksum_tuple = self._get_package_checksum_tuple(package)
            if package_checksum_tuple:
                checksum_type, checksum_value = package_checksum_tuple
                sum_attributes = {'type': checksum_type}
                self.xml_generator.completeElement('sum', sum_attributes, checksum_value)

            if package.get('reboot_suggested'):
                self.xml_generator.completeElement('reboot_suggested', {}, 'True')
            self.xml_generator.endElement('package')

        self.xml_generator.endElement('collection')
        self.xml_generator.endElement('pkglist')
        self.xml_generator.endElement('update')
Ejemplo n.º 9
0
    def test_utf8_writes(self):
        """
        Test that utf-8 non-ascii characters are handled without complaint.
        """
        xml_generator = XMLWriter(StringIO())

        tag = u'𝅘𝅥𝅮'
        xml_generator.startDocument()
        xml_generator.writeDoctype('<!DOCTYPE string here>')
        xml_generator.startElement(tag, {u'𝆑': u'𝆒'})
        xml_generator.completeElement(u'inner_tag2 Ώ',
                                      {u'attr1 ỳ': u'value1 ỳ'}, u'ỳ')
        xml_generator.endElement(tag)
        xml_generator.endDocument()
Ejemplo n.º 10
0
class UpdateinfoXMLFileContext(XmlFileContext):
    def __init__(self, working_dir, nevra_in_repo, checksum_type=None, conduit=None,
                 updateinfo_checksum_type=None):
        """
        Creates and writes updateinfo XML data.

        :param working_dir: The working directory for the request
        :type working_dir:  basestring
        :param nevra_in_repo: The nevra of all rpms in the repo.
        :type nevra_in_repo:  set of models.NEVRA objects
        :param checksum_type: The type of checksum to be used
        :type checksum_type:  basestring
        :param conduit: A conduit to use
        :type conduit:  pulp.plugins.conduits.repo_publish.RepoPublishConduit
        :param updateinfo_checksum_type: The type of checksum to be used in the package list
        :type updateinfo_checksum_type:  basestring
        """
        self.nevra_in_repo = nevra_in_repo
        metadata_file_path = os.path.join(working_dir, REPO_DATA_DIR_NAME,
                                          UPDATE_INFO_XML_FILE_NAME)
        self.conduit = conduit
        super(UpdateinfoXMLFileContext, self).__init__(
            metadata_file_path, 'updates', checksum_type=checksum_type)
        self.updateinfo_checksum_type = updateinfo_checksum_type
        self.optional_errata_fields = ('title', 'release', 'rights', 'solution', 'severity',
                                       'summary', 'pushcount')
        self.mandatory_errata_fields = ('description',)

    def _open_metadata_file_handle(self):
        """
        Open the metadata file handle, creating any missing parent directories.
        If the file already exists, this will overwrite it.
        """
        super(XmlFileContext, self)._open_metadata_file_handle()
        self.xml_generator = XMLWriter(self.metadata_file_handle, short_empty_elements=True)

    def _get_repo_unit_nevra(self, erratum_unit):
        """
        Return a list of NEVRA dicts for units in a single repo referenced by the given errata.

        Pulp errata units combine the known packages from all synced repos. Given an errata unit
        and a repo, return a list of NEVRA dicts that can be used to filter out packages not
        linked to that repo when generating a repo's updateinfo XML file.

        :param erratum_unit: The erratum unit that should be written to updateinfo.xml.
        :type erratum_unit: pulp_rpm.plugins.db.models.Errata

        :return: a list of NEVRA dicts for units in a single repo referenced by the given errata
        :rtype: list
        """
        nevra_in_repo_and_pkglist = []
        for pkglist in erratum_unit.pkglist:
            for pkg in pkglist['packages']:
                pkg_nevra = models.NEVRA(name=pkg['name'], epoch=pkg['epoch'] or '0',
                                         version=pkg['version'], release=pkg['release'],
                                         arch=pkg['arch'])
                if pkg_nevra in self.nevra_in_repo:
                    pkg_dict = {
                        'name': pkg_nevra.name,
                        'epoch': pkg_nevra.epoch,
                        'version': pkg_nevra.version,
                        'release': pkg_nevra.release,
                        'arch': pkg_nevra.arch,
                    }
                    nevra_in_repo_and_pkglist.append(pkg_dict)
        return nevra_in_repo_and_pkglist

    def _get_package_checksum_tuple(self, package):
        """
        Decide which checksum to publish for the given package in the erratum package list.
        If updateinfo_checksum_type is requested explicitly, the checksum of this type will be
        published.
        If no checksum_type is requested, the checksum of the distributor checksum type
        will be published, if available. Otherwise the longest one will be chosen.

        Handle two possible ways of specifying the checksum in the erratum package list:
        - in the `sum` package field as a list of alternating checksum types and values,
          e.g. ['type1', 'checksum1', 'type2', 'checksum2']
        - in the `type` and `sums` package fields. It is only the case when the erratum was uploaded
          via pulp-admin. Only one type of the checksum could be specified this way.

        :param package: package from the erratum package list
        :type  package: dict
        :return: checksum type and value to publish. An empty tuple is returned if there is
                 no checksum available.
        :rtype: tuple
        :raises PulpCodedException: if updateinfo_checksum_type is not available
        """
        package_checksum_tuple = ()
        dist_checksum_type = self.checksum_type
        package_checksums = package.get('sum') or []
        if package.get('type'):
            package_checksums += [package['type'], package.get('sums')]

        for checksum_type in (self.updateinfo_checksum_type, dist_checksum_type):
            try:
                checksum_index = package_checksums.index(checksum_type) + 1
            except (ValueError, IndexError):
                # raise exception if updateinfo_checksum_type is unavailable
                if self.updateinfo_checksum_type and \
                   checksum_type == self.updateinfo_checksum_type:
                    raise PulpCodedException(error_codes.RPM1012,
                                             checksumtype=self.updateinfo_checksum_type)
                continue
            else:
                checksum_value = package_checksums[checksum_index]
                package_checksum_tuple = (checksum_type, checksum_value)
                break
        else:
            if package_checksums:
                # choose the longest(the best?) checksum available
                checksum_value = max(package_checksums[1::2], key=len)
                checksum_type_index = package_checksums.index(checksum_value) - 1
                checksum_type = package_checksums[checksum_type_index]
                package_checksum_tuple = (checksum_type, checksum_value)

        return package_checksum_tuple

    def add_unit_metadata(self, item):
        """
        Write the XML representation of erratum_unit to self.metadata_file_handle
        (updateinfo.xml.gx).

        :param item: The erratum unit that should be written to updateinfo.xml.
        :type  item: pulp_rpm.plugins.db.models.Errata
        """
        erratum_unit = item
        update_attributes = {'status': erratum_unit.status,
                             'type': erratum_unit.type,
                             'version': erratum_unit.version,
                             'from': erratum_unit.errata_from or ''}
        self.xml_generator.startElement('update', update_attributes)
        self.xml_generator.completeElement('id', {}, erratum_unit.errata_id)
        issued_attributes = {'date': erratum_unit.issued}
        self.xml_generator.completeElement('issued', issued_attributes, '')

        if erratum_unit.reboot_suggested:
            self.xml_generator.completeElement('reboot_suggested', {}, 'True')

        for element in self.optional_errata_fields:
            element_value = getattr(erratum_unit, element)
            if not element_value:
                continue
            self.xml_generator.completeElement(element, {}, unicode(element_value))

        for element in self.mandatory_errata_fields:
            element_value = getattr(erratum_unit, element)
            element_value = '' if element_value is None else element_value
            self.xml_generator.completeElement(element, {}, unicode(element_value))

        updated = erratum_unit.updated
        if updated:
            updated_attributes = {'date': updated}
            self.xml_generator.completeElement('updated', updated_attributes, '')

        self.xml_generator.startElement('references')
        for reference in erratum_unit.references:
            reference_attributes = {'id': reference['id'] or '',
                                    'title': reference['title'] or '',
                                    'type': reference['type'],
                                    'href': reference['href']}
            self.xml_generator.completeElement('reference', reference_attributes, '')
        self.xml_generator.endElement('references')

        # If we can pull a repo_id off the conduit, use that to generate repo-specific nevra
        if self.conduit and hasattr(self.conduit, 'repo_id'):
            repo_unit_nevra = self._get_repo_unit_nevra(erratum_unit)
        else:
            repo_unit_nevra = None

        seen_pkglists = set()
        for pkglist in erratum_unit.pkglist:
            packages = tuple(sorted(p['filename'] for p in pkglist['packages']))
            if packages in seen_pkglists:
                continue
            seen_pkglists.add(packages)

            self.xml_generator.startElement('pkglist')
            collection_attributes = {}
            short = pkglist.get('short')
            if short is not None:
                collection_attributes['short'] = short
            self.xml_generator.startElement('collection', collection_attributes)
            self.xml_generator.completeElement('name', {}, pkglist['name'])

            for package in pkglist['packages']:
                package_attributes = {'name': package['name'],
                                      'version': package['version'],
                                      'release': package['release'],
                                      'epoch': package['epoch'] or '0',
                                      'arch': package['arch'],
                                      'src': package.get('src', '') or ''}
                if repo_unit_nevra is not None:
                    # If repo_unit_nevra can be used for comparison, take the src attr out of a
                    # copy of this package's attrs to get a nevra dict for comparison
                    package_nevra = package_attributes.copy()
                    del(package_nevra['src'])
                    if package_nevra not in repo_unit_nevra:
                        # current package not in the specified repo, don't add it to the output
                        continue

                self.xml_generator.startElement('package', package_attributes)
                self.xml_generator.completeElement('filename', {}, package['filename'])

                package_checksum_tuple = self._get_package_checksum_tuple(package)
                if package_checksum_tuple:
                    checksum_type, checksum_value = package_checksum_tuple
                    sum_attributes = {'type': checksum_type}
                    self.xml_generator.completeElement('sum', sum_attributes, checksum_value)

                if package.get('reboot_suggested'):
                    self.xml_generator.completeElement('reboot_suggested', {}, 'True')
                self.xml_generator.endElement('package')

            self.xml_generator.endElement('collection')
            self.xml_generator.endElement('pkglist')
        self.xml_generator.endElement('update')
Ejemplo n.º 11
0
class PackageXMLFileContext(XmlFileContext):
    """
    The PackageXMLFileContext is used to generate the comps.xml file used in yum repositories
    for storing information about package categories and package groups
    """
    def __init__(self, working_dir, checksum_type=None):
        """
        Initialize and set the file where the metadata is being saved.

        :param working_dir: root working directory where the repo is being generated.
        :type  working_dir: str
        """

        metadata_file_path = os.path.join(working_dir, REPO_DATA_DIR_NAME,
                                          PACKAGE_XML_FILE_NAME)
        super(PackageXMLFileContext,
              self).__init__(metadata_file_path,
                             'comps',
                             checksum_type=checksum_type)

    def _open_metadata_file_handle(self):
        """
        Open the metadata file handle, creating any missing parent directories.
        If the file already exists, this will overwrite it.
        """
        super(PackageXMLFileContext, self)._open_metadata_file_handle()
        self.xml_generator = XMLWriter(self.metadata_file_handle,
                                       short_empty_elements=True)

    def _write_file_header(self):
        """
        Write out the beginning of the comps.xml file
        """
        self.xml_generator.startDocument()
        doctype = '<!DOCTYPE comps PUBLIC "-//Red Hat, Inc.//DTD Comps info//EN" "comps.dtd">'
        self.xml_generator.writeDoctype(doctype)
        self.xml_generator.startElement(self.root_tag, self.root_attributes)

    def _write_translated_fields(self, tag_name, translated_fields):
        """
        Write out the xml for a translated field

        :param tag_name: The xml tag name to generate for the translated field
        :type tag_name: str
        :param translated_fields: The dictionary of locales and the translated text
        :type translated_fields: dict of locale to translated text
        """
        if translated_fields:
            for locale, field_text in sorted(translated_fields.iteritems()):
                self.xml_generator.completeElement(tag_name,
                                                   {'xml:lang': locale},
                                                   field_text)

    def add_package_group_unit_metadata(self, group_unit):
        """
        Write out the XML representation of a group

        :param group_unit: the group to publish
        :type group_unit: pulp_rpm.plugins.db.models.PackageGroup
        """
        self.xml_generator.startElement('group', {})
        self.xml_generator.completeElement('id', {},
                                           group_unit.package_group_id)
        self.xml_generator.completeElement('default', {},
                                           str(group_unit.default).lower())
        self.xml_generator.completeElement(
            'uservisible', {},
            str(group_unit.user_visible).lower())

        # If the order is not specified, then 1024 should be set as default.
        # With value of 1024 the group will be displayed at the very bottom of the list.
        display_order = group_unit.display_order if group_unit.display_order is not None else 1024
        self.xml_generator.completeElement('display_order', {},
                                           str(display_order))

        if group_unit.langonly:
            self.xml_generator.completeElement('langonly', {},
                                               group_unit.langonly)
        self.xml_generator.completeElement('name', {}, group_unit.name)
        self._write_translated_fields('name', group_unit.translated_name)
        self.xml_generator.completeElement('description', {},
                                           group_unit.description)
        self._write_translated_fields('description',
                                      group_unit.translated_description)

        self.xml_generator.startElement('packagelist', {})
        if group_unit.mandatory_package_names:
            for pkg in sorted(group_unit.mandatory_package_names):
                self.xml_generator.completeElement('packagereq',
                                                   {'type': 'mandatory'}, pkg)
        if group_unit.default_package_names:
            for pkg in sorted(group_unit.default_package_names):
                self.xml_generator.completeElement('packagereq',
                                                   {'type': 'default'}, pkg)
        if group_unit.optional_package_names:
            for pkg in sorted(group_unit.optional_package_names):
                self.xml_generator.completeElement('packagereq',
                                                   {'type': 'optional'}, pkg)
        if group_unit.conditional_package_names:
            for pkg_name, value in group_unit.conditional_package_names:
                attrs = {'type': 'conditional', 'requires': value}
                self.xml_generator.completeElement('packagereq', attrs,
                                                   pkg_name)
        self.xml_generator.endElement('packagelist')
        self.xml_generator.endElement('group')

    def add_package_category_unit_metadata(self, unit):
        """
        Write out the XML representation of a category unit

        :param unit: The category to publish
        :type unit: pulp_rpm.plugins.db.models.PackageCategory
        """
        self.xml_generator.startElement('category')
        self.xml_generator.completeElement('id', {}, unit.package_category_id)

        # If the order is not specified, then 1024 should be set as default.
        # With value of 1024 the group will be displayed at the very bottom of the list.
        display_order = unit.display_order if unit.display_order is not None else 1024
        self.xml_generator.completeElement('display_order', {},
                                           str(display_order))

        self.xml_generator.completeElement('name', {}, unit.name)
        self._write_translated_fields('name', unit.translated_name)
        self.xml_generator.completeElement('description', {}, unit.description)
        self._write_translated_fields('description',
                                      unit.translated_description)

        self.xml_generator.startElement('grouplist', {})
        if unit.packagegroupids:
            for groupid in sorted(unit.packagegroupids):
                self.xml_generator.completeElement('groupid', {}, groupid)
        self.xml_generator.endElement('grouplist')
        self.xml_generator.endElement('category')

    def add_package_environment_unit_metadata(self, unit):
        """
        Write out the XML representation of a environment group unit

        :param unit: The environment group to publish
        :type unit: pulp_rpm.plugins.db.models.PackageEnvironment
        """
        self.xml_generator.startElement('environment', {})
        self.xml_generator.completeElement('id', {},
                                           unit.package_environment_id)

        # If the order is not specified, then 1024 should be set as default.
        # With value of 1024 the group will be displayed at the very bottom of the list.
        display_order = unit.display_order if unit.display_order is not None else 1024
        self.xml_generator.completeElement('display_order', {},
                                           str(display_order))

        self.xml_generator.completeElement('name', {}, unit.name)
        self._write_translated_fields('name', unit.translated_name)
        self.xml_generator.completeElement('description', {}, unit.description)
        self._write_translated_fields('description',
                                      unit.translated_description)

        self.xml_generator.startElement('grouplist', {})
        if unit.group_ids:
            for groupid in sorted(unit.group_ids):
                self.xml_generator.completeElement('groupid', {}, groupid)
        self.xml_generator.endElement('grouplist')

        self.xml_generator.startElement('optionlist', {})
        if unit.options:
            for option in sorted(unit.options):
                if option['default']:
                    attributes = {'default': 'true'}
                    self.xml_generator.completeElement('groupid', attributes,
                                                       option['group'])
                else:
                    self.xml_generator.completeElement('groupid', {},
                                                       option['group'])

        self.xml_generator.endElement('optionlist')
        self.xml_generator.endElement('environment')

    def add_package_langpacks_unit_metadata(self, unit):
        """
        Write out the XML representation of a PackageLangpacks unit

        :param unit: The langpacks unit to publish
        :type unit: pulp_rpm.plugins.db.models.PackageLangpacks
        """
        self.xml_generator.startElement('langpacks', {})
        for match_dict in unit.matches:
            self.xml_generator.completeElement('match', match_dict, '')
        self.xml_generator.endElement('langpacks')
Ejemplo n.º 12
0
class UpdateinfoXMLFileContext(XmlFileContext):
    def __init__(self, working_dir, checksum_type=None, conduit=None):
        metadata_file_path = os.path.join(working_dir, REPO_DATA_DIR_NAME,
                                          UPDATE_INFO_XML_FILE_NAME)
        self.conduit = conduit
        super(UpdateinfoXMLFileContext, self).__init__(
            metadata_file_path, 'updates', checksum_type=checksum_type)
        self.optional_errata_fields = ('title', 'release', 'rights', 'solution', 'severity',
                                       'summary', 'pushcount')
        self.mandatory_errata_fields = ('description',)

    def _open_metadata_file_handle(self):
        """
        Open the metadata file handle, creating any missing parent directories.
        If the file already exists, this will overwrite it.
        """
        super(XmlFileContext, self)._open_metadata_file_handle()
        self.xml_generator = XMLWriter(self.metadata_file_handle, short_empty_elements=True)

    def _repo_unit_nevra(self, erratum_unit, repo_id):
        """
        Return a list of NEVRA dicts for units in a single repo referenced by the given errata.

        Pulp errata units combine the known packages from all synced repos. Given an errata unit
        and a repo, return a list of NEVRA dicts that can be used to filter out packages not
        linked to that repo when generating a repo's updateinfo XML file. While returning that
        list of NEVRA dicts is the main goal, doing so quickly and without running out of memory
        is what makes this a little bit tricky.

        Build up a super-fancy query to get the unit ids for all NEVRA seen in these errata
        check repo/unit associations for this errata to limit the packages in the published
        updateinfo to the units in the repo being currently published.

        :param erratum_unit: The erratum unit that should be written to updateinfo.xml.
        :type erratum_unit: pulp_rpm.plugins.db.models.Errata
        :param repo_id: The repo_id of a pulp repository in which to find units
        :type repo_id: str
        :return: a list of NEVRA dicts for units in a single repo referenced by the given errata
        :rtype: list
        """
        nevra_fields = ('name', 'epoch', 'version', 'release', 'arch')
        nevra_q = mongoengine.Q()
        for pkglist in erratum_unit.pkglist:
            for pkg in pkglist['packages']:
                pkg_nevra = dict((field, pkg[field]) for field in nevra_fields)
                nevra_q |= mongoengine.Q(**pkg_nevra)
        # Aim the super-fancy query at mongo to get the units that this errata refers to
        # The scaler method on the end returns a list of tuples to try to save some memory
        # and also cut down on mongoengine model instance hydration costs.
        nevra_units = models.RPM.objects.filter(nevra_q).scalar('id', *nevra_fields)

        # Split up the nevra unit entries into a mapping of the unit id to its nevra fields
        nevra_unit_map = dict((nevra_unit[0], nevra_unit[1:]) for nevra_unit in nevra_units)

        # Get all of the unit ids from this errata that are associated with the current repo.
        # Cast this as a set for speedier lookups when iterating of the nevra unit map.
        repo_unit_ids = set(RepositoryContentUnit.objects.filter(
            unit_id__in=nevra_unit_map.keys(), repo_id=repo_id).scalar('unit_id'))

        # Finally(!), intersect the repo unit ids with the unit nevra ids to
        # create a list of nevra dicts that can be easily compared to the
        # errata package nevra and exclude unrelated packages
        repo_unit_nevra = []
        for nevra_unit_id, nevra_field_values in nevra_unit_map.items():
            # based on the args to scalar when nevra_units was created:
            if nevra_unit_id in repo_unit_ids:
                repo_unit_nevra.append(dict(zip(nevra_fields, nevra_field_values)))

        return repo_unit_nevra

    def add_unit_metadata(self, item):
        """
        Write the XML representation of erratum_unit to self.metadata_file_handle
        (updateinfo.xml.gx).

        :param item: The erratum unit that should be written to updateinfo.xml.
        :type  item: pulp_rpm.plugins.db.models.Errata
        """
        erratum_unit = item
        update_attributes = {'status': erratum_unit.status,
                             'type': erratum_unit.type,
                             'version': erratum_unit.version,
                             'from': erratum_unit.errata_from or ''}
        self.xml_generator.startElement('update', update_attributes)
        self.xml_generator.completeElement('id', {}, erratum_unit.errata_id)
        issued_attributes = {'date': erratum_unit.issued}
        self.xml_generator.completeElement('issued', issued_attributes, '')

        if erratum_unit.reboot_suggested is None:
            erratum_unit.reboot_suggested = False
        reboot_suggested_str = str(erratum_unit.reboot_suggested)
        self.xml_generator.completeElement('reboot_suggested', {}, reboot_suggested_str)

        for element in self.optional_errata_fields:
            element_value = getattr(erratum_unit, element)
            if not element_value:
                continue
            self.xml_generator.completeElement(element, {}, unicode(element_value))

        for element in self.mandatory_errata_fields:
            element_value = getattr(erratum_unit, element)
            element_value = '' if element_value is None else element_value
            self.xml_generator.completeElement(element, {}, unicode(element_value))

        updated = erratum_unit.updated
        if updated:
            updated_attributes = {'date': updated}
            self.xml_generator.completeElement('updated', updated_attributes, '')

        self.xml_generator.startElement('references')
        for reference in erratum_unit.references:
            reference_attributes = {'id': reference['id'] or '',
                                    'title': reference['title'] or '',
                                    'type': reference['type'],
                                    'href': reference['href']}
            self.xml_generator.completeElement('reference', reference_attributes, '')
        self.xml_generator.endElement('references')

        # If we can pull a repo_id off the conduit, use that to generate repo-specific nevra
        if self.conduit and hasattr(self.conduit, 'repo_id'):
            repo_unit_nevra = self._repo_unit_nevra(erratum_unit, self.conduit.repo_id)
        else:
            repo_unit_nevra = None

        seen_pkglists = set()
        for pkglist in erratum_unit.pkglist:
            packages = tuple(sorted(p['filename'] for p in pkglist['packages']))
            if packages in seen_pkglists:
                continue
            seen_pkglists.add(packages)

            self.xml_generator.startElement('pkglist')
            collection_attributes = {}
            short = pkglist.get('short')
            if short is not None:
                collection_attributes['short'] = short
            self.xml_generator.startElement('collection', collection_attributes)
            self.xml_generator.completeElement('name', {}, pkglist['name'])

            for package in pkglist['packages']:
                package_attributes = {'name': package['name'],
                                      'version': package['version'],
                                      'release': package['release'],
                                      'epoch': package['epoch'] or '0',
                                      'arch': package['arch'],
                                      'src': package.get('src', '') or ''}
                if repo_unit_nevra is not None:
                    # If repo_unit_nevra can be used for comparison, take the src attr out of a
                    # copy of this package's attrs to get a nevra dict for comparison
                    package_nevra = package_attributes.copy()
                    del(package_nevra['src'])
                    if package_nevra not in repo_unit_nevra:
                        # current package not in the specified repo, don't add it to the output
                        continue

                self.xml_generator.startElement('package', package_attributes)
                self.xml_generator.completeElement('filename', {}, package['filename'])

                checksum_tuple = package.get('sum', None)
                if checksum_tuple is not None:
                    checksum_type, checksum_value = checksum_tuple
                    sum_attributes = {'type': checksum_type}
                    self.xml_generator.completeElement('sum', sum_attributes, checksum_value)

                reboot_suggested_str = str(package.get('reboot_suggested', False))
                self.xml_generator.completeElement('reboot_suggested', {}, reboot_suggested_str)
                self.xml_generator.endElement('package')

            self.xml_generator.endElement('collection')
            self.xml_generator.endElement('pkglist')
        self.xml_generator.endElement('update')
Ejemplo n.º 13
0
class PackageXMLFileContext(XmlFileContext):
    """
    The PackageXMLFileContext is used to generate the comps.xml file used in yum repositories
    for storing information about package categories and package groups
    """

    def __init__(self, working_dir, checksum_type=None):
        """
        Initialize and set the file where the metadata is being saved.

        :param working_dir: root working directory where the repo is being generated.
        :type  working_dir: str
        """

        metadata_file_path = os.path.join(working_dir, REPO_DATA_DIR_NAME, PACKAGE_XML_FILE_NAME)
        super(PackageXMLFileContext, self).__init__(metadata_file_path, 'comps',
                                                    checksum_type=checksum_type)

    def _open_metadata_file_handle(self):
        """
        Open the metadata file handle, creating any missing parent directories.
        If the file already exists, this will overwrite it.
        """
        super(PackageXMLFileContext, self)._open_metadata_file_handle()
        self.xml_generator = XMLWriter(self.metadata_file_handle, short_empty_elements=True)

    def _write_file_header(self):
        """
        Write out the beginning of the comps.xml file
        """
        self.xml_generator.startDocument()
        doctype = '<!DOCTYPE comps PUBLIC "-//Red Hat, Inc.//DTD Comps info//EN" "comps.dtd">'
        self.xml_generator.writeDoctype(doctype)
        self.xml_generator.startElement(self.root_tag, self.root_attributes)

    def _write_translated_fields(self, tag_name, translated_fields):
        """
        Write out the xml for a translated field

        :param tag_name: The xml tag name to generate for the translated field
        :type tag_name: str
        :param translated_fields: The dictionary of locales and the translated text
        :type translated_fields: dict of locale to translated text
        """
        if translated_fields:
            for locale, field_text in sorted(translated_fields.iteritems()):
                self.xml_generator.completeElement(tag_name, {'xml:lang': locale}, field_text)

    def add_package_group_unit_metadata(self, group_unit):
        """
        Write out the XML representation of a group

        :param group_unit: the group to publish
        :type group_unit: pulp_rpm.plugins.db.models.PackageGroup
        """
        self.xml_generator.startElement('group', {})
        self.xml_generator.completeElement('id', {}, group_unit.package_group_id)
        self.xml_generator.completeElement('default', {}, str(group_unit.default).lower())
        self.xml_generator.completeElement('uservisible', {}, str(group_unit.user_visible).lower())

        # If the order is not specified, then 1024 should be set as default.
        # With value of 1024 the group will be displayed at the very bottom of the list.
        display_order = group_unit.display_order if group_unit.display_order is not None else 1024
        self.xml_generator.completeElement('display_order', {}, str(display_order))

        if group_unit.langonly:
            self.xml_generator.completeElement('langonly', {}, group_unit.langonly)
        self.xml_generator.completeElement('name', {}, group_unit.name)
        self._write_translated_fields('name', group_unit.translated_name)
        self.xml_generator.completeElement('description', {}, group_unit.description)
        self._write_translated_fields('description', group_unit.translated_description)

        self.xml_generator.startElement('packagelist', {})
        if group_unit.mandatory_package_names:
            for pkg in sorted(group_unit.mandatory_package_names):
                self.xml_generator.completeElement('packagereq', {'type': 'mandatory'}, pkg)
        if group_unit.default_package_names:
            for pkg in sorted(group_unit.default_package_names):
                self.xml_generator.completeElement('packagereq', {'type': 'default'}, pkg)
        if group_unit.optional_package_names:
            for pkg in sorted(group_unit.optional_package_names):
                self.xml_generator.completeElement('packagereq', {'type': 'optional'}, pkg)
        if group_unit.conditional_package_names:
            for pkg_name, value in group_unit.conditional_package_names:
                attrs = {'type': 'conditional', 'requires': value}
                self.xml_generator.completeElement('packagereq', attrs, pkg_name)
        self.xml_generator.endElement('packagelist')
        self.xml_generator.endElement('group')

    def add_package_category_unit_metadata(self, unit):
        """
        Write out the XML representation of a category unit

        :param unit: The category to publish
        :type unit: pulp_rpm.plugins.db.models.PackageCategory
        """
        self.xml_generator.startElement('category')
        self.xml_generator.completeElement('id', {}, unit.package_category_id)

        # If the order is not specified, then 1024 should be set as default.
        # With value of 1024 the group will be displayed at the very bottom of the list.
        display_order = unit.display_order if unit.display_order is not None else 1024
        self.xml_generator.completeElement('display_order', {}, str(display_order))

        self.xml_generator.completeElement('name', {}, unit.name)
        self._write_translated_fields('name', unit.translated_name)
        self.xml_generator.completeElement('description', {}, unit.description)
        self._write_translated_fields('description', unit.translated_description)

        self.xml_generator.startElement('grouplist', {})
        if unit.packagegroupids:
            for groupid in sorted(unit.packagegroupids):
                self.xml_generator.completeElement('groupid', {}, groupid)
        self.xml_generator.endElement('grouplist')
        self.xml_generator.endElement('category')

    def add_package_environment_unit_metadata(self, unit):
        """
        Write out the XML representation of a environment group unit

        :param unit: The environment group to publish
        :type unit: pulp_rpm.plugins.db.models.PackageEnvironment
        """
        self.xml_generator.startElement('environment', {})
        self.xml_generator.completeElement('id', {}, unit.package_environment_id)

        # If the order is not specified, then 1024 should be set as default.
        # With value of 1024 the group will be displayed at the very bottom of the list.
        display_order = unit.display_order if unit.display_order is not None else 1024
        self.xml_generator.completeElement('display_order', {}, str(display_order))

        self.xml_generator.completeElement('name', {}, unit.name)
        self._write_translated_fields('name', unit.translated_name)
        self.xml_generator.completeElement('description', {}, unit.description)
        self._write_translated_fields('description', unit.translated_description)

        self.xml_generator.startElement('grouplist', {})
        if unit.group_ids:
            for groupid in sorted(unit.group_ids):
                self.xml_generator.completeElement('groupid', {}, groupid)
        self.xml_generator.endElement('grouplist')

        self.xml_generator.startElement('optionlist', {})
        if unit.options:
            for option in sorted(unit.options):
                if option['default']:
                    attributes = {'default': 'true'}
                    self.xml_generator.completeElement('groupid', attributes, option['group'])
                else:
                    self.xml_generator.completeElement('groupid', {}, option['group'])

        self.xml_generator.endElement('optionlist')
        self.xml_generator.endElement('environment')

    def add_package_langpacks_unit_metadata(self, unit):
        """
        Write out the XML representation of a PackageLangpacks unit

        :param unit: The langpacks unit to publish
        :type unit: pulp_rpm.plugins.db.models.PackageLangpacks
        """
        self.xml_generator.startElement('langpacks', {})
        for match_dict in unit.matches:
            self.xml_generator.completeElement('match', match_dict, '')
        self.xml_generator.endElement('langpacks')