def _verify_advisory_rpms_in_container_build(self, errata_id,
                                                 container_build_id):
        """
        verify container built on brew has the latest rpms from an advisory
        """
        if self.dry_run:
            return (True, '')

        # Get rpms in advisory. There can be multiple versions of RPMs with
        # the same name, so we group them by a name in `advisory_rpms_by_name`
        # and use set of the nvrs as a value.
        advisory_rpms_by_name = {}
        e = Errata()
        build_nvrs = e.get_builds(errata_id)
        if build_nvrs:
            with koji_service(conf.koji_profile,
                              log,
                              login=False,
                              dry_run=self.dry_run) as session:
                for build_nvr in build_nvrs:
                    build_rpms = session.get_build_rpms(build_nvr)
                    for rpm in build_rpms:
                        if rpm['name'] not in advisory_rpms_by_name:
                            advisory_rpms_by_name[rpm['name']] = set()
                        advisory_rpms_by_name[rpm['name']].add(rpm['nvr'])

        # get rpms in container
        with koji_service(conf.koji_profile,
                          log,
                          login=False,
                          dry_run=self.dry_run) as session:
            container_rpms = session.get_rpms_in_container(container_build_id)
            container_rpms_by_name = {
                rpmlib.parse_nvr(x)['name']: x
                for x in container_rpms
            }

        # For each RPM name in advisory, check that the RPM exists in the
        # built container and its version is the same as one RPM in the
        # advisory.
        unmatched_rpms = []
        for rpm_name, nvrs in advisory_rpms_by_name.items():
            if rpm_name not in container_rpms_by_name:
                continue
            container_rpm_nvr = container_rpms_by_name[rpm_name]
            if container_rpm_nvr not in nvrs:
                unmatched_rpms.append(rpm_name)

        if unmatched_rpms:
            msg = ("The following RPMs in container build (%s) do not match "
                   "with the latest RPMs in advisory (%s):\n%s" %
                   (container_build_id, errata_id, unmatched_rpms))
            return (False, msg)
        return (True, "")
    def _filter_bundles_by_pinned_related_images(self, bundle_image_nvrs):
        """
        If the digests were not pinned by OSBS, the bundle image nvr
        will be filtered out.

        There is no need in checking pinning for every of related images,
        because we already know that digest points to the manifest list,
        because of previous filtering.

        :param set bundle_image_nvrs: NVRs of operator bundles
        :return: list of NVRs of bundle images that have at least one
            original related image that was rebuilt
        """
        ret_bundle_images_nvrs = set()
        with koji_service(conf.koji_profile,
                          log,
                          dry_run=self.dry_run,
                          login=False) as session:
            for nvr in bundle_image_nvrs:
                build = session.get_build(nvr)
                if not build:
                    log.error("Could not find the build %s in Koji", nvr)
                    continue
                related_images = (build.get("build", {}).get("extra", {}).get(
                    "image", {}).get("operator_manifests",
                                     {}).get("related_images", {}))

                # Skip the bundle if the related images section was not populated by OSBS
                if related_images.get("created_by_osbs") is not True:
                    continue
                ret_bundle_images_nvrs.add(nvr)
        return ret_bundle_images_nvrs
예제 #3
0
    def _get_packages_for_compose(self, nvr):
        """Get RPMs of current build NVR

        :param str nvr: build NVR.
        :return: list of RPM names built from given build.
        :rtype: list
        """
        with koji_service(conf.koji_profile, log,
                          dry_run=self.handler.dry_run) as session:
            rpms = session.get_build_rpms(nvr)
        return list(set([rpm['name'] for rpm in rpms]))
예제 #4
0
    def build_container(self,
                        scm_url,
                        branch,
                        target,
                        repo_urls=None,
                        isolated=False,
                        release=None,
                        koji_parent_build=None,
                        arch_override=None,
                        compose_ids=None,
                        operator_csv_modifications_url=None):
        """
        Build a container in Koji.

        :param str scm_url: refer to ``KojiService.build_container``.
        :param str branch: refer to ``KojiService.build_container``.
        :param str target: refer to ``KojiService.build_container``.
        :param list[str] repo_urls: refer to ``KojiService.build_container``.
        :param bool isolated: refer to ``KojiService.build_container``.
        :param str release: refer to ``KojiService.build_container``.
        :param str koji_parent_build: refer to ``KojiService.build_container``.
        :param str arch_override: refer to ``KojiService.build_container``.
        :param list[int] compose_ids: refer to ``KojiService.build_container``.
        :param str operator_csv_modifications_url: refer to ``KojiService.build_container``.
        :return: task id returned from Koji buildContainer API.
        :rtype: int
        """
        with koji_service(profile=conf.koji_profile,
                          logger=log,
                          dry_run=self.dry_run) as service:
            log.info(
                'Building container from source: %s, '
                'release=%r, parent=%r, target=%r, arch=%r, compose_ids=%r',
                scm_url, release, koji_parent_build, target, arch_override,
                compose_ids)

            return service.build_container(
                scm_url,
                branch,
                target,
                repo_urls=repo_urls,
                isolated=isolated,
                release=release,
                koji_parent_build=koji_parent_build,
                arch_override=arch_override,
                scratch=conf.koji_container_scratch_build,
                compose_ids=compose_ids,
                operator_csv_modifications_url=operator_csv_modifications_url,
            )
예제 #5
0
    def _get_compose_source(self, nvr):
        """Get tag from which to collect packages to compose
        :param str nvr: build NVR used to find correct tag.
        :return: found tag. None is returned if build is not the latest build
            of found tag.
        :rtype: str
        """
        with koji_service(conf.koji_profile, log,
                          dry_run=self.handler.dry_run) as service:
            # Get the list of *-candidate tags, because packages added into
            # Errata should be tagged into -candidate tag.
            tags = service.session.listTags(nvr)
            candidate_tags = [
                tag['name'] for tag in tags
                if tag['name'].endswith('-candidate')
            ]

            # Candidate tags may include unsigned packages and ODCS won't
            # allow generating compose from them, so try to find out final
            # version of candidate tag (without the "-candidate" suffix).
            final_tags = []
            for candidate_tag in candidate_tags:
                final = candidate_tag[:-len("-candidate")]
                final_tags += [
                    tag['name'] for tag in tags if tag['name'] == final
                ]

            # Prefer final tags over candidate tags.
            tags_to_try = final_tags + candidate_tags
            for tag in tags_to_try:
                latest_build = service.session.listTagged(
                    tag, latest=True, package=koji.parse_NVR(nvr)['name'])
                if latest_build and latest_build[0]['nvr'] == nvr:
                    self.handler.log_info(
                        "Package %r is latest version in tag %r, "
                        "will use this tag", nvr, tag)
                    return tag
                elif not latest_build:
                    self.handler.log_info(
                        "Could not find package %r in tag %r, "
                        "skipping this tag", nvr, tag)
                else:
                    self.handler.log_info(
                        "Package %r is not he latest in the tag %r ("
                        "latest is %r), skipping this tag", nvr, tag,
                        latest_build[0]['nvr'])
    def update_db_build_state(self, build_id, found_build, event):
        """ Update build state in db. """
        if event.new_state == 'CLOSED':
            # if build is triggered by an advisory, verify the container
            # contains latest RPMs from the advisory
            if found_build.event.event_type_id == EVENT_TYPES[
                    ErrataAdvisoryRPMsSignedEvent]:
                errata_id = found_build.event.search_key
                # build_id is actually task id in build system, find out the actual build first
                with koji_service(conf.koji_profile,
                                  log,
                                  login=False,
                                  dry_run=self.dry_run) as session:
                    container_build_id = session.get_container_build_id_from_task(
                        build_id)

                ret, msg = self._verify_advisory_rpms_in_container_build(
                    errata_id, container_build_id)
                if ret:
                    found_build.transition(ArtifactBuildState.DONE.value,
                                           "Built successfully.")
                else:
                    found_build.transition(ArtifactBuildState.FAILED.value,
                                           msg)

            # for other builds, mark them as DONE
            else:
                found_build.transition(ArtifactBuildState.DONE.value,
                                       "Built successfully.")
        if event.new_state == 'FAILED':
            args = json.loads(found_build.build_args)
            if "retry_count" not in args:
                args["retry_count"] = 0
            args["retry_count"] += 1
            found_build.build_args = json.dumps(args)
            if args["retry_count"] < 3:
                found_build.transition(
                    ArtifactBuildState.PLANNED.value,
                    "Retrying failed build %s" % (str(found_build.build_id)))
                self.start_to_build_images([found_build])
            else:
                found_build.transition(ArtifactBuildState.FAILED.value,
                                       "Failed to build in Koji.")
        db.session.commit()
예제 #7
0
    def check_unfinished_koji_tasks(self, session):
        stale_date = datetime.utcnow() - timedelta(days=7)
        db_events = session.query(models.Event).filter(
            models.Event.state == EventState.BUILDING.value,
            models.Event.time_created >= stale_date).all()

        for db_event in db_events:
            for build in db_event.builds:
                if build.state != ArtifactBuildState.BUILD.value:
                    continue
                if build.build_id <= 0:
                    continue
                with koji_service(
                        conf.koji_profile, log, login=False) as koji_session:
                    task = koji_session.get_task_info(build.build_id)
                    task_states = {v: k for k, v in koji.TASK_STATES.items()}
                    new_state = task_states[task["state"]]
                    if new_state not in ["FAILED", "CLOSED"]:
                        continue
                    event = BrewContainerTaskStateChangeEvent(
                        "fake event", build.name, None, None, build.build_id,
                        "BUILD", new_state)
                    work_queue_put(event)
    def handle(self, event):
        """
        Handle Freshmaker manage request to cancel actions triggered by
        event, given by event_id in the event.body. This especially
        means to cancel running Koji builds. If some of the builds
        couldn't be canceled for some reason, there's ongoing event
        containing only those builds (by DB id).
        """

        failed_to_cancel_builds_id = []
        log_fail = log.error if event.last_try else log.warning
        with koji_service(conf.koji_profile, log,
                          dry_run=event.dry_run) as session:
            builds = db.session.query(ArtifactBuild).filter(
                ArtifactBuild.id.in_(event.body['builds_id'])).all()
            for build in builds:
                if session.cancel_build(build.build_id):
                    build.state_reason = 'Build canceled in external build system.'
                    continue
                if event.last_try:
                    build.state_reason = (
                        'Build was NOT canceled in external build system.'
                        ' Max number of tries reached!')
                failed_to_cancel_builds_id.append(build.id)
            db.session.commit()

        if failed_to_cancel_builds_id:
            log_fail(
                "Builds which failed to cancel in external build system,"
                " by DB id: %s; try #%s", failed_to_cancel_builds_id,
                event.try_count)
        if event.last_try or not failed_to_cancel_builds_id:
            return []

        event.body['builds_id'] = failed_to_cancel_builds_id
        return [event]
예제 #9
0
    def filter_images_based_on_dist_git_branch(self, images, db_event):
        """
        Filter images based on the dist-git branch requested by the user. If the images were never
        be built for that branch, let's skip the build.
        The input images are all the images matching a specific name. In this method we also select
        the images with higher NVR for the same name (package).

        :param images list: images to rebuild.
        :param db_event Event: event object in the db.
        :return: list of images to rebuild. If the event gets skipped, return empty list.
        :rtype: list
        """
        with koji_service(conf.koji_profile,
                          log,
                          dry_run=conf.dry_run,
                          login=False) as session:

            # Sort images by nvr
            images = sorted_by_nvr(images, reverse=True)

            # Use a dict to map a package (name) to the highest NVR image. For example:
            # {"ubi8-container": ContainerImage<nvr=ubi8-container-8.1-100>,
            # "nodejs-12-container": ContainerImage<nvr=nodejs12-container-1.0-101>)}
            images_to_rebuild = {}

            # In case the user requested to build ['s2i-core-container', 'cnv-libvirt-container']
            # lightblue will return a bunch of NVRs for each name, example:
            #   * s2i-core-container-1-127
            #   * s2i-core-container-1-126
            #   * ...
            #   * cnv-libvirt-container-1.3-1
            #   * cnv-libvirt-container-1.2-4
            #   * ...
            # Since `images` is a list of sorted NVRs, we just need to select the first NVR for
            # each package (name).
            for image in images:
                build = None
                git_branch = None

                package = image['brew']['package']
                # if package is already in images_to_rebuild we don't need to keep searching
                # since the images were sorted by NVR in the beginning
                if package not in images_to_rebuild:
                    # Let's get the branch for this image
                    build = session.get_build(image.nvr)
                    task_id = build.get("extra",
                                        {}).get("container_koji_task_id")
                    if task_id:
                        task = session.get_task_request(task_id)
                        # The task_info should always be in the 3rd element
                        task_info = task[2]
                        git_branch = task_info.get("git_branch") if len(
                            task_info) else None

                    if (build and task_id and git_branch
                            and self.event.dist_git_branch == git_branch):
                        images_to_rebuild[package] = image

            if not images_to_rebuild or len(images_to_rebuild) < len(
                    self.event.container_images):
                # If we didn't find images to rebuild, or we found less than what the user asked
                # it means that some of those images were never been built before for the requested
                # branch. In this case we need to throw an error because we won't build something
                # that was never built before.
                # We cannot return to the API with an error, because the request already completed
                # at this point. Let's mark this build as FAILED then.
                msg = (
                    "One or more of the requested image was never built before for the "
                    f"requested branch: {self.event.dist_git_branch}. "
                    "Cannot build it, please change your request.")
                missing_images = set(self.event.container_images) - set(
                    images_to_rebuild.keys())
                if missing_images:
                    msg += f" Problematic images are {missing_images}"
                db_event.transition(EventState.FAILED, msg)
                db.session.commit()
                self.log_info(msg)
                return []

            # The result needs to be a list
            return list(images_to_rebuild.values())