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
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]))
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, )
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()
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]
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())