Пример #1
0
 def eject_from_mash(self, update, reason):
     update.locked = False
     text = '%s ejected from the push because %r' % (update.title, reason)
     log.warn(text)
     update.comment(self.db, text, author=u'bodhi')
     # Remove the pending tag as well
     if update.request is UpdateRequest.stable:
         update.remove_tag(update.release.pending_stable_tag,
                           koji=buildsys.get_session())
     elif update.request is UpdateRequest.testing:
         update.remove_tag(update.release.pending_testing_tag,
                           koji=buildsys.get_session())
     update.request = None
     if update.title in self.state['updates']:
         self.state['updates'].remove(update.title)
     if update in self.updates:
         self.updates.remove(update)
     notifications.publish(
         topic="update.eject",
         msg=dict(
             repo=self.id,
             update=update,
             reason=reason,
             request=self.request,
             release=self.release,
             agent=self.agent,
         ),
         force=True,
     )
Пример #2
0
def get_rpm_header(nvr, tries=0):
    """
    Get the rpm header for a given build.

    Args:
        nvr (basestring): The name-version-release string of the build you want headers for.
        tries (int): The number of attempts that have been made to retrieve the nvr so far. Defaults
            to 0.
    Returns:
        dict: A dictionary mapping RPM header names to their values, as returned by the Koji client.
    """
    tries += 1
    headers = [
        'name', 'summary', 'version', 'release', 'url', 'description',
        'changelogtime', 'changelogname', 'changelogtext',
    ]
    rpmID = nvr + '.src'
    koji_session = buildsys.get_session()
    try:
        result = koji_session.getRPMHeaders(rpmID=rpmID, headers=headers)
    except Exception as e:
        msg = "Failed %i times to get rpm header data from koji for %s:  %s"
        log.warning(msg % (tries, nvr, str(e)))
        if tries < 3:
            # Try again...
            return get_rpm_header(nvr, tries=tries)
        else:
            # Give up for good and re-raise the failure...
            raise

    if result:
        return result

    raise ValueError("No rpm headers found in koji for %r" % nvr)
Пример #3
0
def main():
    """Check build tags and sign those we missed."""
    db_factory = transactional_session_maker()
    older_than = datetime.utcnow() - timedelta(
        days=config.get('check_signed_builds_delay'))
    with db_factory() as session:
        updates = models.Update.query.filter(
            models.Update.status == models.UpdateStatus.pending).filter(
                models.Update.release_id == models.Release.id).filter(
                    models.Release.state.in_([
                        models.ReleaseState.current,
                        models.ReleaseState.pending,
                        models.ReleaseState.frozen,
                    ])).all()

        if len(updates) == 0:
            log.debug('No stuck Updates found')
            return

        kc = buildsys.get_session()
        stuck_builds = []

        for update in updates:
            # Let Bodhi have its times
            if update.date_submitted >= older_than:
                continue
            builds = update.builds
            # Clean Updates with no builds
            if len(builds) == 0:
                log.debug(f'Obsoleting empty update {update.alias}')
                update.obsolete(session)
                session.flush()
                continue
            pending_signing_tag = update.release.pending_signing_tag
            pending_testing_tag = update.release.pending_testing_tag
            for build in builds:
                if build.signed:
                    log.debug(f'{build.nvr} already marked as signed')
                    continue
                build_tags = [t['name'] for t in kc.listTags(build=build.nvr)]
                if pending_signing_tag not in build_tags and pending_testing_tag in build_tags:
                    # Our composer missed the message that the build got signed
                    log.debug(f'Changing signed status of {build.nvr}')
                    build.signed = True
                if pending_signing_tag in build_tags and pending_testing_tag not in build_tags:
                    # autosign missed the message that the build is waiting to be signed
                    log.debug(
                        f'{build.nvr} is stuck waiting to be signed, let\'s try again'
                    )
                    stuck_builds.append(build.nvr)
            session.flush()

        if stuck_builds:
            kc.multicall = True
            for b in stuck_builds:
                kc.untagBuild(pending_signing_tag, b, force=True)
            kc.multiCall()
            for b in stuck_builds:
                kc.tagBuild(pending_signing_tag, b, force=True)
            kc.multiCall()
Пример #4
0
    def _perform_tag_actions(self):
        koji = buildsys.get_session()
        for i, batches in enumerate([(self.add_tags_sync, self.move_tags_sync),
                                     (self.add_tags_async, self.move_tags_async)]):
            add, move = batches
            if i == 0:
                koji.multicall = False
            else:
                koji.multicall = True
            for action in add:
                tag, build = action
                self.log.info("Adding tag %s to %s" % (tag, build))
                koji.tagBuild(tag, build, force=True)
            for action in move:
                from_tag, to_tag, build = action
                self.log.info('Moving %s from %s to %s' % (
                              build, from_tag, to_tag))
                koji.moveBuild(from_tag, to_tag, build, force=True)

            if i != 0:
                results = koji.multiCall()
                failed_tasks = buildsys.wait_for_tasks([task[0] for task in results],
                                                       koji, sleep=15)
                if failed_tasks:
                    raise Exception("Failed to move builds: %s" % failed_tasks)
Пример #5
0
    def __call__(self, message: fedora_messaging.api.Message) -> None:
        """Comment on related update.

        Args:
            message: The message we are processing.
        """
        body = message.body

        missing = []
        for mandatory in ('contact', 'run', 'artifact', 'pipeline', 'test',
                          'generated_at', 'version'):
            if mandatory not in body:
                missing.append(mandatory)
        if missing:
            log.debug(f"Received incomplete CI message. Missing: {', '.join(missing)}")
            return

        nvr = body['artifact'].get('nvr', None)
        build_id = body['pipeline'].get('id', None)
        run_url = body['run'].get('url', None)

        koji = buildsys.get_session()

        if not nvr and build_id:
            kbuildinfo = koji.getBuild(build_id)
            log.debug(kbuildinfo)
            if not kbuildinfo:
                log.debug(f"Can't find Koji build with id '{build_id}'.")
                return
            elif 'nvr' not in kbuildinfo:
                log.debug(f"Koji build info with id '{build_id}' doesn't contain 'nvr'.")
                return
            else:
                nvr = kbuildinfo['nvr']
        elif not nvr and not build_id:
            log.debug("Received incomplete CI message. Missing: 'artifact.nvr', 'pipeline.id'.")
            return

        with self.db_factory() as dbsession:
            build = dbsession.query(Build).filter_by(nvr=nvr).first()
            if not build:
                log.debug(f"Can't get build for '{nvr}'.")
                return

            if build.update:
                if build.update.from_tag:
                    log.debug("Update is created from tag. Skipping comment.")
                    return
                comment = "CI testing started."
                if run_url:
                    comment = f"CI testing started: '{run_url}'."
                build.update.comment(
                    dbsession, text=comment, author='bodhi', email_notification=False)
            else:
                log.debug(f"No update in Bodhi for '{nvr}'. Nothing to comment on.")
                return

            log.debug("Committing changes to the database.")
            dbsession.commit()
Пример #6
0
def main(argv=sys.argv):
    """
    Remove the pending and testing tags from branched updates.

    Args:
        argv (list): The arguments passed to the script. Defaults to sys.argv.
    """
    if len(argv) != 2:
        usage(argv)

    config_uri = argv[1]

    setup_logging(config_uri)
    log = logging.getLogger(__name__)

    settings = get_appsettings(config_uri)
    initialize_db(settings)
    db = Session()
    koji = buildsys.get_session()
    one_day = timedelta(days=1)
    now = datetime.utcnow()

    try:
        for release in db.query(Release).filter_by(
                state=ReleaseState.pending).all():
            log.info(release.name)
            for update in db.query(Update).filter_by(
                    release=release, status=UpdateStatus.stable).all():
                if now - update.date_stable > one_day:
                    for build in update.builds:
                        tags = build.get_tags()
                        stable_tag = release.dist_tag
                        testing_tag = release.testing_tag
                        pending_signing_tag = release.pending_signing_tag
                        pending_testing_tag = release.pending_testing_tag
                        if stable_tag not in tags:
                            log.error('%s not tagged as stable %s' %
                                      (build.nvr, tags))
                            continue
                        if testing_tag in tags:
                            log.info('Removing %s from %s' %
                                     (testing_tag, build.nvr))
                            koji.untagBuild(testing_tag, build.nvr)
                        if pending_signing_tag in tags:
                            log.info('Removing %s from %s' %
                                     (pending_signing_tag, build.nvr))
                            koji.untagBuild(pending_signing_tag, build.nvr)
                        if pending_testing_tag in tags:
                            log.info('Removing %s from %s' %
                                     (pending_testing_tag, build.nvr))
                            koji.untagBuild(pending_testing_tag, build.nvr)
        db.commit()
    except Exception as e:
        log.error(e)
        db.rollback()
        Session.remove()
        sys.exit(1)
Пример #7
0
 def test_build_tag_when_cache_tags_fails(self, mock_cache_tags):
     """Assert that the validator fails if getting tags from koji fails"""
     self.request.validated = {'builds': ['foo-1-1.f17']}
     self.request.buildinfo = {'foo-1-1.f17': {
         'nvr': ('foo', '1-1', 'f17'),
     }}
     self.request.koji = buildsys.get_session()
     mock_cache_tags.return_value = None
     result = validators.validate_build_tags(self.request)
     assert result is None
Пример #8
0
def get_koji(request):
    """
    Return a Koji client, or a duck-type of a Koji client, depending on config.

    Args:
        request (pyramid.request.Request): The current web request. Unused.
    Returns:
        koji.ClientSession or DevBuildSys: A Koji client, or a dev Koji mock.
    """
    return buildsys.get_session()
Пример #9
0
def get_all_packages():
    """
    Return a list of all packages in Koji.

    Returns:
        list: The list of package_names from the koji.listPackages() call.
    """
    log.debug('Fetching list of all packages...')
    koji = buildsys.get_session()
    return [pkg['package_name'] for pkg in koji.listPackages()]
Пример #10
0
    def test_tag_pending_signing_side_tag(self):
        update = self.db.query(models.Update).first()
        update.from_tag = "f17-build-side-1234"
        side_tag_pending_signing = "f17-build-side-1234-signing-pending"
        self.db.commit()

        tag_update_builds_main(side_tag_pending_signing, update.builds)

        koji = buildsys.get_session()
        assert (side_tag_pending_signing, update.builds[0]) in koji.__added__
    def test_side_tag_composed_by_bodhi(self):
        u = self.db.query(models.Update).first()
        from_tag = "f17-build-side-1234"
        builds = [b.nvr for b in u.builds]
        handle_srtags_main(builds, u.release.pending_signing_tag, from_tag,
                           None, u.release.candidate_tag)

        koji = buildsys.get_session()
        assert ('f17-updates-signing-pending',
                'bodhi-2.0-1.fc17') in koji.__added__
        assert ('f17-updates-candidate', 'bodhi-2.0-1.fc17') in koji.__added__
Пример #12
0
    def test_side_tag_not_composed_by_bodhi(self):
        u = self.db.query(models.Update).first()
        from_tag = "f32-build-side-1234"
        side_tag_signing_pending = u.release.get_pending_signing_side_tag(from_tag)
        side_tag_testing_pending = u.release.get_testing_side_tag(from_tag)
        builds = [b.nvr for b in u.builds]
        handle_srtags_main(builds, side_tag_signing_pending, from_tag,
                           side_tag_testing_pending, None)

        koji = buildsys.get_session()
        assert ('f32-build-side-1234-signing-pending', 'bodhi-2.0-1.fc17') in koji.__added__
        assert "f32-build-side-1234-signing-pending" in koji.__tags__[0][0]
        assert "f32-build-side-1234-testing-pending" in koji.__tags__[1][0]
Пример #13
0
    def test_build_tag_when_cache_tags_fails_cache_release(self, mock_cache_tags):
        """Assert that the cache_release returns None if getting tags
        with cache_tags from koji fails"""
        self.request.validated = {'builds': ['foo-1-1.f17']}
        self.request.buildinfo = {'foo-1-1.f17': {
            'nvr': ('foo', '1-1', 'f17'), 'tags': ['tag'],
        }}
        self.request.from_tag_inherited = []
        self.request.koji = buildsys.get_session()

        mock_cache_tags.side_effect = [['tag'], None]

        result = validators.validate_build_tags(self.request)
        assert result is None
Пример #14
0
    def test_unable_to_infer_content_type_not_implemented(self):
        """Test error handler when Bodhi can't determine the content type due to NotImplemented."""
        request = self.get_mock_request()
        request.koji = buildsys.get_session()
        request.validated = {'builds': [b.nvr for b in models.Build.query.all()]}

        with mock.patch('bodhi.server.validators.ContentType.infer_content_class',
                        side_effect=NotImplementedError('oh no')):
            validators.validate_acls(request)

        assert request.errors == [
            {'location': 'body', 'name': 'builds',
             'description': "Unable to infer content_type.  'oh no'"}
        ]
        assert request.errors.status == 501
Пример #15
0
 def remove_pending_tags(self):
     """ Remove all pending tags from these updates """
     self.log.debug("Removing pending tags from builds")
     koji = buildsys.get_session()
     koji.multicall = True
     for update in self.updates:
         if update.request is UpdateRequest.stable:
             update.remove_tag(update.release.pending_stable_tag,
                               koji=koji)
         elif update.request is UpdateRequest.testing:
             update.remove_tag(update.release.pending_testing_tag,
                               koji=koji)
     result = koji.multiCall()
     self.log.debug('remove_pending_tags koji.multiCall result = %r',
                    result)
Пример #16
0
def main(tag: str, builds: typing.List[str]):
    """Handle tagging builds for an update in Koji.

    Args:
        tag: a koji tag to apply on the builds.
        builds: list of new build added to the update.
    """
    try:
        kc = buildsys.get_session()
        kc.multicall = True
        for build in builds:
            kc.tagBuild(tag, build)
            log.info(f"Tagging build {build} in {tag}")
        kc.multiCall()
    except Exception:
        log.exception("There was an error handling tagging builds in koji.")
Пример #17
0
 def _fetch_updates(self):
     """Based on our given koji tag, populate a list of Update objects."""
     log.debug("Fetching builds tagged with '%s'" % self.tag)
     kojiBuilds = get_session().listTagged(self.tag, latest=True)
     nonexistent = []
     log.debug("%d builds found" % len(kojiBuilds))
     for build in kojiBuilds:
         self.builds[build['nvr']] = build
         build_obj = self.db.query(Build).filter_by(nvr=six.text_type(build['nvr'])).first()
         if build_obj:
             if build_obj.update:
                 self.updates.add(build_obj.update)
             else:
                 log.warn('%s does not have a corresponding update' % build['nvr'])
         else:
             nonexistent.append(build['nvr'])
     if nonexistent:
         log.warning("Couldn't find the following koji builds tagged as "
                     "%s in bodhi: %s" % (self.tag, nonexistent))
Пример #18
0
def main(builds: typing.List[str], pending_signing_tag: str, from_tag: str,
         pending_testing_tag: typing.Optional[str],
         candidate_tag: typing.Optional[str]):
    """Handle side-tags and related tags for updates in Koji.

    Args:
        builds: a list of builds to be tagged.
        pending_signing_tag: the pending signing tag to apply on the builds.
        from_tag: the tag into which the builds were built.
        pending_testing_tag: the pending_testing_tag to create if not None.
        candidate_tag: the candidate tag needed for update that are composed by bodhi.
    """
    try:
        koji = buildsys.get_session()

        tags = [pending_signing_tag]
        if pending_testing_tag is not None:
            # Validate that <koji_tag>-pending-signing and <koji-tag>-testing-signing exists
            # if not create them.
            if not koji.getTag(pending_signing_tag):
                log.info(f"Create {pending_signing_tag} in koji")
                koji.createTag(pending_signing_tag, parent=from_tag)
            if not koji.getTag(pending_testing_tag):
                log.info(f"Create {pending_testing_tag} in koji")
                koji.createTag(pending_testing_tag, parent=from_tag)
                koji.editTag2(pending_testing_tag, perm="autosign")
        elif candidate_tag is not None:
            # If we don't provide a pending_testing_tag, then we have merged the
            # side tag into the release pending_signing and candidate tag.
            # We can remove the side tag.
            tags.append(candidate_tag)
            koji.removeSideTag(from_tag)

        koji.multicall = True
        for b in builds:
            for t in tags:
                log.info(f"Tagging build {b} in {t}")
                koji.tagBuild(t, b)
        koji.multiCall()

    except Exception:
        log.exception("There was an error handling side-tags updates")
Пример #19
0
    def test_unable_to_infer_content_type(self):
        """Test the error handler for when Bodhi cannot determine the content type of a build."""
        request = self.get_mock_request()
        request.koji = buildsys.get_session()
        request.validated = {
            'builds': [b.nvr for b in models.Build.query.all()]
        }

        with mock.patch(
                'bodhi.server.validators.ContentType.infer_content_class',
                side_effect=IOError('oh no')):
            validators.validate_acls(request)

        self.assertEqual(
            request.errors,
            [{
                'location': 'body',
                'name': 'builds',
                'description': "Unable to infer content_type.  'oh no'"
            }])
        self.assertEqual(request.errors.status, 400)
Пример #20
0
def _get_build_repository(build):
    """
    Return the registry repository name for the given Build from the container's pull string.

    Examples -
    'candidate-registry.fedoraproject.org/f29/cockpit:176-5.fc28' => 'f29/cockpit'.
    'candidate-registry.fedoraproject.org/myrepo@sha256:<hash>' => 'myrepo'.

    Args:
        build (bodhi.server.models.Build): A Build representing a container or flatpak.
    Returns:
        str: The registry repository name for the build.
    """
    koji = buildsys.get_session()
    koji_build = koji.getBuild(build.nvr)

    pull_specs = koji_build['extra']['image']['index']['pull']
    # All the pull specs should have the same repository, so which one we use is arbitrary
    base, tag = re.compile(r'[:@]').split(pull_specs[0], 1)
    server, repository = base.split('/', 1)

    return repository
Пример #21
0
    def add_update(self, update):
        """Generate the extended metadata for a given update"""
        rec = cr.UpdateRecord()
        rec.version = __version__
        rec.fromstr = config.get('bodhi_email')
        rec.status = update.status.value
        rec.type = update.type.value
        rec.id = to_bytes(update.alias)
        rec.title = to_bytes(update.title)
        rec.summary = to_bytes('%s %s update' %
                               (update.get_title(), update.type.value))
        rec.description = to_bytes(update.notes)
        rec.release = to_bytes(update.release.long_name)
        rec.rights = config.get('updateinfo_rights')

        if update.date_pushed:
            rec.issued_date = update.date_pushed
        if update.date_modified:
            rec.updated_date = update.date_modified

        col = cr.UpdateCollection()
        col.name = to_bytes(update.release.long_name)
        col.shortname = to_bytes(update.release.name)

        koji = get_session()
        for build in update.builds:
            rpms = self.get_rpms(koji, build.nvr)
            for rpm in rpms:
                pkg = cr.UpdateCollectionPackage()
                pkg.name = rpm['name']
                pkg.version = rpm['version']
                pkg.release = rpm['release']
                if rpm['epoch'] is not None:
                    pkg.epoch = str(rpm['epoch'])
                else:
                    pkg.epoch = '0'
                pkg.arch = rpm['arch']

                # TODO: how do we handle UpdateSuggestion.logout, etc?
                pkg.reboot_suggested = update.suggest is UpdateSuggestion.reboot

                filename = '%s.%s.rpm' % (rpm['nvr'], rpm['arch'])
                pkg.filename = filename

                # Build the URL
                if rpm['arch'] == 'src':
                    arch = 'SRPMS'
                elif rpm['arch'] in ('noarch', 'i686'):
                    arch = 'i386'
                else:
                    arch = rpm['arch']

                pkg.src = os.path.join(
                    config.get('file_url'),
                    update.status is UpdateStatus.testing and 'testing' or '',
                    str(update.release.version), arch, filename[0], filename)

                col.append(pkg)

        rec.append_collection(col)

        # Create references for each bug
        for bug in update.bugs:
            ref = cr.UpdateReference()
            ref.type = 'bugzilla'
            ref.id = to_bytes(bug.bug_id)
            ref.href = to_bytes(bug.url)
            ref.title = to_bytes(bug.title)
            rec.append_reference(ref)

        # Create references for each CVE
        for cve in update.cves:
            ref = cr.UpdateReference()
            ref.type = 'cve'
            ref.id = to_bytes(cve.cve_id)
            ref.href = to_bytes(cve.url)
            rec.append_reference(ref)

        self.uinfo.append(rec)
Пример #22
0
 def test_tag_pending_signing_builds(self):
     update = self.db.query(models.Update).first()
     pending_signing_tag = update.release.pending_signing_tag
     tag_update_builds_main(pending_signing_tag, update.builds)
     koji = buildsys.get_session()
     assert (pending_signing_tag, update.builds[0]) in koji.__added__
Пример #23
0
def approve_update(update: Update, db: Session):
    """Add a comment to an update if it is ready for stable.

    Check that the update is eligible to be pushed to stable but hasn't had comments from Bodhi to
    this effect. Add a comment stating that the update may now be pushed to stable.

    Args:
        update: an update in testing that may be ready for stable.
    """
    if not update.release.mandatory_days_in_testing and not update.autotime:
        # If this release does not have any testing requirements and is not autotime,
        # skip it
        log.info(
            f"{update.release.name} doesn't have mandatory days in testing")
        return
    # If this update was already commented, skip it
    if update.has_stable_comment:
        return
    # If updates have reached the testing threshold, say something! Keep in mind
    # that we don't care about karma here, because autokarma updates get their request set
    # to stable by the Update.comment() workflow when they hit the required threshold. Thus,
    # this function only needs to consider the time requirements because these updates have
    # not reached the karma threshold.
    if not update.meets_testing_requirements:
        return
    log.info(f'{update.alias} now meets testing requirements')
    # Only send email notification about the update reaching
    # testing approval on releases composed by bodhi
    update.comment(db,
                   str(config.get('testing_approval_msg')),
                   author='bodhi',
                   email_notification=update.release.composed_by_bodhi)
    notifications.publish(
        update_schemas.UpdateRequirementsMetStableV1.from_dict(
            dict(update=update)))
    if update.autotime and update.days_in_testing >= update.stable_days:
        log.info(f"Automatically marking {update.alias} as stable")
        # For now only rawhide update can be created using side tag
        # Do not add the release.pending_stable_tag if the update
        # was created from a side tag.
        if update.release.composed_by_bodhi:
            update.set_request(db=db,
                               action=UpdateRequest.stable,
                               username="******")
        # For updates that are not included in composes run by bodhi itself,
        # mark them as stable
        else:
            # Single and Multi build update
            conflicting_builds = update.find_conflicting_builds()
            if conflicting_builds:
                builds_str = str.join(", ", conflicting_builds)
                update.comment(
                    db, "This update cannot be pushed to stable. "
                    f"These builds {builds_str} have a more recent "
                    f"build in koji's {update.release.stable_tag} tag.",
                    author="bodhi")
                update.request = None
                if update.from_tag is not None:
                    update.status = UpdateStatus.pending
                    update.remove_tag(
                        update.release.get_testing_side_tag(update.from_tag))
                else:
                    update.status = UpdateStatus.obsolete
                    update.remove_tag(update.release.pending_testing_tag)
                    update.remove_tag(update.release.candidate_tag)
                db.commit()
                return
            update.add_tag(update.release.stable_tag)
            update.status = UpdateStatus.stable
            update.request = None
            update.pushed = True
            update.date_stable = update.date_pushed = func.current_timestamp()
            update.comment(
                db,
                "This update has been submitted for stable by bodhi",
                author=u'bodhi')
            update.modify_bugs()
            db.commit()
            # Multi build update
            if update.from_tag:
                # Merging the side tag should happen here
                pending_signing_tag = update.release.get_pending_signing_side_tag(
                    update.from_tag)
                testing_tag = update.release.get_testing_side_tag(
                    update.from_tag)
                update.remove_tag(pending_signing_tag)
                update.remove_tag(testing_tag)
                update.remove_tag(update.from_tag)
                koji = buildsys.get_session()
                koji.deleteTag(pending_signing_tag)
                koji.deleteTag(testing_tag)

            else:
                # Single build update
                update.remove_tag(update.release.pending_testing_tag)
                update.remove_tag(update.release.pending_stable_tag)
                update.remove_tag(update.release.pending_signing_tag)
                update.remove_tag(update.release.candidate_tag)
Пример #24
0
def main(argv=sys.argv):
    """
    Comment on updates that are eligible to be pushed to stable.

    Queries for updates in the testing state that have a NULL request, looping over them looking for
    updates that are eligible to be pushed to stable but haven't had comments from Bodhi to this
    effect. For each such update it finds it will add a comment stating that the update may now be
    pushed to stable.

    This function is the entry point for the bodhi-approve-testing console script.

    Args:
        argv (list): A list of command line arguments. Defaults to sys.argv.
    """
    logging.basicConfig(level=logging.ERROR)

    if len(argv) != 2:
        usage(argv)

    settings = get_appsettings(argv[1])
    initialize_db(settings)
    db = Session()
    buildsys.setup_buildsystem(config)

    try:
        testing = db.query(Update).filter_by(status=UpdateStatus.testing,
                                             request=None)
        for update in testing:
            if not update.release.mandatory_days_in_testing and not update.autotime:
                # If this release does not have any testing requirements and is not autotime,
                # skip it
                print(
                    f"{update.release.name} doesn't have mandatory days in testing"
                )
                continue

            # If this update was already commented, skip it
            if update.has_stable_comment:
                continue

            # If updates have reached the testing threshold, say something! Keep in mind
            # that we don't care about karma here, because autokarma updates get their request set
            # to stable by the Update.comment() workflow when they hit the required threshold. Thus,
            # this function only needs to consider the time requirements because these updates have
            # not reached the karma threshold.
            if update.meets_testing_requirements:
                print(f'{update.alias} now meets testing requirements')
                # Only send email notification about the update reaching
                # testing approval on releases composed by bodhi
                update.comment(
                    db,
                    str(config.get('testing_approval_msg')),
                    author='bodhi',
                    email_notification=update.release.composed_by_bodhi)

                notifications.publish(
                    update_schemas.UpdateRequirementsMetStableV1.from_dict(
                        dict(update=update)))

                if update.autotime and update.days_in_testing >= update.stable_days:
                    print(f"Automatically marking {update.alias} as stable")
                    # For now only rawhide update can be created using side tag
                    # Do not add the release.pending_stable_tag if the update
                    # was created from a side tag.
                    if update.release.composed_by_bodhi:
                        update.set_request(db=db,
                                           action=UpdateRequest.stable,
                                           username="******")
                    # For updates that are not included in composes run by bodhi itself,
                    # mark them as stable
                    else:
                        # Single and Multi build update
                        conflicting_builds = update.find_conflicting_builds()
                        if conflicting_builds:
                            builds_str = str.join(", ", conflicting_builds)
                            update.comment(
                                db, "This update cannot be pushed to stable. "
                                f"These builds {builds_str} have a more recent "
                                f"build in koji's {update.release.stable_tag} tag.",
                                author="bodhi")
                            update.request = None
                            if update.from_tag is not None:
                                update.status = UpdateStatus.pending
                                update.remove_tag(
                                    update.release.get_testing_side_tag(
                                        update.from_tag))
                            else:
                                update.status = UpdateStatus.obsolete
                                update.remove_tag(
                                    update.release.pending_testing_tag)
                                update.remove_tag(update.release.candidate_tag)
                            db.commit()
                            continue

                        update.add_tag(update.release.stable_tag)
                        update.status = UpdateStatus.stable
                        update.request = None
                        update.pushed = True
                        update.date_stable = update.date_pushed = func.current_timestamp(
                        )
                        update.comment(
                            db,
                            "This update has been submitted for stable by bodhi",
                            author=u'bodhi')

                        # Multi build update
                        if update.from_tag:
                            # Merging the side tag should happen here
                            pending_signing_tag = update.release.get_pending_signing_side_tag(
                                update.from_tag)
                            testing_tag = update.release.get_testing_side_tag(
                                update.from_tag)

                            update.remove_tag(pending_signing_tag)
                            update.remove_tag(testing_tag)
                            update.remove_tag(update.from_tag)

                            koji = buildsys.get_session()
                            koji.deleteTag(pending_signing_tag)
                            koji.deleteTag(testing_tag)

                            # Removes the tag and the build target from koji.
                            koji.removeSideTag(update.from_tag)
                        else:
                            # Single build update
                            update.remove_tag(
                                update.release.pending_testing_tag)
                            update.remove_tag(
                                update.release.pending_stable_tag)
                            update.remove_tag(
                                update.release.pending_signing_tag)
                            update.remove_tag(update.release.candidate_tag)

                db.commit()

    except Exception as e:
        print(str(e))
        db.rollback()
        Session.remove()
        sys.exit(1)
Пример #25
0
 def test_buildsys_lock(self, mock_lock, mock_buildsystem):
     """Assert the buildsystem lock is aquired and released in get_session"""
     buildsys.get_session()
     mock_lock.__enter__.assert_called_once()
     mock_lock.__exit__.assert_called_once()
     mock_buildsystem.assert_called_once_with()
Пример #26
0
def new_update(request):
    """
    Save an update.

    This entails either creating a new update, or editing an existing one. To
    edit an existing update, the update's alias must be specified in
    the ``edited`` parameter.

    If the ``from_tag`` parameter is specified and ``builds`` is missing or
    empty, the list of builds will be filled with the latest builds in this
    Koji tag. This is done by validate_from_tag() because the list of builds
    needs to be available in validate_acls().
    Ensure that related tags ``from_tag``-pending-signing and ``from_tag``-testing
    exists and if not create them in Koji.

    Args:
        request (pyramid.request): The current request.
    """
    data = request.validated
    log.debug('validated = %s' % data)

    # This has already been validated at this point, but we need to ditch
    # it since the models don't care about a csrf argument.
    data.pop('csrf_token')

    # Same here, but it can be missing.
    data.pop('builds_from_tag', None)

    build_nvrs = data.get('builds', [])
    from_tag = data.get('from_tag')

    caveats = []
    try:

        releases = set()
        builds = []

        # Create the Package and Build entities
        for nvr in build_nvrs:
            name, version, release = request.buildinfo[nvr]['nvr']

            package = Package.get_or_create(request.buildinfo[nvr])

            # Also figure out the build type and create the build if absent.
            build_class = ContentType.infer_content_class(
                base=Build, build=request.buildinfo[nvr]['info'])
            build = build_class.get(nvr)

            if build is None:
                log.debug("Adding nvr %s, type %r", nvr, build_class)
                build = build_class(nvr=nvr, package=package)
                request.db.add(build)
                request.db.flush()

            build.package = package
            build.release = request.buildinfo[build.nvr]['release']
            builds.append(build)
            releases.add(request.buildinfo[build.nvr]['release'])

        # We want to go ahead and commit the transaction now so that the Builds are in the database.
        # Otherwise, there will be a race condition between robosignatory signing the Builds and the
        # signed handler attempting to mark the builds as signed. When we lose that race, the signed
        # handler doesn't see the Builds in the database and gives up. After that, nothing will mark
        # the builds as signed.
        request.db.commit()

        # After we commit the transaction, we need to get the builds and releases again,
        # since they were tied to the previous session that has now been terminated.
        builds = []
        releases = set()
        for nvr in build_nvrs:
            # At this moment, we are sure the builds are in the database (that is what the commit
            # was for actually).
            build = Build.get(nvr)
            builds.append(build)
            releases.add(build.release)

        if data.get('edited'):

            log.info('Editing update: %s' % data['edited'])

            data['release'] = list(releases)[0]
            data['builds'] = [b.nvr for b in builds]
            data['from_tag'] = from_tag
            result, _caveats = Update.edit(request, data)
            caveats.extend(_caveats)
        else:
            if len(releases) > 1:
                caveats.append({
                    'name': 'releases',
                    'description': 'Your update is being split '
                    'into %i, one for each release.' % len(releases)

                })
            updates = []
            for release in releases:
                _data = copy.copy(data)  # Copy it because .new(..) mutates it
                _data['builds'] = [b for b in builds if b.release == release]
                _data['release'] = release
                _data['from_tag'] = from_tag

                log.info('Creating new update: %r' % _data['builds'])
                result, _caveats = Update.new(request, _data)
                log.debug('%s update created', result.alias)

                updates.append(result)
                caveats.extend(_caveats)

            if len(releases) > 1:
                result = dict(updates=updates)

            if from_tag:
                koji = buildsys.get_session()
                for update in updates:
                    # Validate that <koji_tag>-pending-signing and <koji-tag>-testing exists,
                    # if not create them
                    side_tag_pending_signing = update.release.get_pending_signing_side_tag(
                        from_tag)
                    side_tag_testing = update.release.get_testing_side_tag(from_tag)
                    if not koji.getTag(side_tag_pending_signing):
                        koji.createTag(side_tag_pending_signing, parent=from_tag)
                    if not koji.getTag(side_tag_testing):
                        koji.createTag(side_tag_testing, parent=from_tag)

                    to_tag = side_tag_pending_signing
                    # Move every new build to <from_tag>-pending-signing tag
                    update.add_tag(to_tag)

    except LockedUpdateException as e:
        log.warning(str(e))
        request.errors.add('body', 'builds', "%s" % str(e))
        return
    except Exception as e:
        log.exception('Failed to create update')
        request.errors.add(
            'body', 'builds', 'Unable to create update.  %s' % str(e))
        return

    # Obsolete older updates for three different cases...
    # editing an update, submitting a new single update, submitting multiple.

    if isinstance(result, dict):
        updates = result['updates']
    else:
        updates = [result]

    for update in updates:
        try:
            caveats.extend(update.obsolete_older_updates(request.db))
        except Exception as e:
            caveats.append({
                'name': 'update',
                'description': 'Problem obsoleting older updates: %s' % str(e),
            })

    if not isinstance(result, dict):
        result = result.__json__()

    result['caveats'] = caveats

    return result
Пример #27
0
    def add_update(self, update):
        """
        Generate the extended metadata for a given update, adding it to self.uinfo.

        Args:
            update (bodhi.server.models.Update): The Update to be added to self.uinfo.
        """
        rec = cr.UpdateRecord()
        rec.version = __version__
        rec.fromstr = config.get('bodhi_email')
        rec.status = update.status.value
        rec.type = update.type.value
        rec.id = to_bytes(update.alias)
        rec.title = to_bytes(update.title)
        rec.severity = util.severity_updateinfo_str(update.severity.value)
        rec.summary = to_bytes('%s %s update' %
                               (update.get_title(), update.type.value))
        rec.description = to_bytes(update.notes)
        rec.release = to_bytes(update.release.long_name)
        rec.rights = config.get('updateinfo_rights')

        if update.date_pushed:
            rec.issued_date = update.date_pushed
        else:
            # Sometimes we only set the date_pushed after it's pushed out, however,
            # it seems that Satellite does not like update entries without issued_date.
            # Since we know that we are pushing it now, and the next push will get the data
            # correctly, let's just insert utcnow().
            rec.issued_date = datetime.utcnow()
        if update.date_modified:
            rec.updated_date = update.date_modified
        else:
            rec.updated_date = datetime.utcnow()

        col = cr.UpdateCollection()
        col.name = to_bytes(update.release.long_name)
        col.shortname = to_bytes(update.release.name)

        koji = get_session()
        for build in update.builds:
            rpms = self.get_rpms(koji, build.nvr)
            for rpm in rpms:
                pkg = cr.UpdateCollectionPackage()
                pkg.name = rpm['name']
                pkg.version = rpm['version']
                pkg.release = rpm['release']
                if rpm['epoch'] is not None:
                    pkg.epoch = str(rpm['epoch'])
                else:
                    pkg.epoch = '0'
                pkg.arch = rpm['arch']

                # TODO: how do we handle UpdateSuggestion.logout, etc?
                pkg.reboot_suggested = update.suggest is UpdateSuggestion.reboot

                filename = '%s.%s.rpm' % (rpm['nvr'], rpm['arch'])
                pkg.filename = filename

                # Build the URL
                if rpm['arch'] == 'src':
                    arch = 'SRPMS'
                elif rpm['arch'] in ('noarch', 'i686'):
                    arch = 'i386'
                else:
                    arch = rpm['arch']

                pkg.src = os.path.join(
                    config.get('file_url'),
                    update.status is UpdateStatus.testing and 'testing' or '',
                    str(update.release.version), arch, filename[0], filename)

                col.append(pkg)

        rec.append_collection(col)

        # Create references for each bug
        for bug in update.bugs:
            ref = cr.UpdateReference()
            ref.type = 'bugzilla'
            ref.id = to_bytes(bug.bug_id)
            ref.href = to_bytes(bug.url)
            ref.title = to_bytes(bug.title)
            rec.append_reference(ref)

        # Create references for each CVE
        for cve in update.cves:
            ref = cr.UpdateReference()
            ref.type = 'cve'
            ref.id = to_bytes(cve.cve_id)
            ref.href = to_bytes(cve.url)
            rec.append_reference(ref)

        self.uinfo.append(rec)
Пример #28
0
    def __call__(self, message: fedora_messaging.api.Message) -> None:
        """Create updates from appropriately tagged builds.

        Args:
            message: The message we are processing.
        """
        body = message.body

        missing = []
        for mandatory in ('tag', 'build_id', 'name', 'version', 'release'):
            if mandatory not in body:
                missing.append(mandatory)
        if missing:
            log.debug(
                f"Received incomplete tag message. Missing: {', '.join(missing)}"
            )
            return

        btag = body['tag']
        bnvr = '{name}-{version}-{release}'.format(**body)

        koji = buildsys.get_session()

        kbuildinfo = koji.getBuild(bnvr)
        if not kbuildinfo:
            log.debug(f"Can't find Koji build for {bnvr}.")
            return

        if 'nvr' not in kbuildinfo:
            log.debug(f"Koji build info for {bnvr} doesn't contain 'nvr'.")
            return

        if 'owner_name' not in kbuildinfo:
            log.debug(
                f"Koji build info for {bnvr} doesn't contain 'owner_name'.")
            return

        if kbuildinfo['owner_name'] in config.get(
                'automatic_updates_blacklist'):
            log.debug(
                f"{bnvr} owned by {kbuildinfo['owner_name']} who is listed in "
                "automatic_updates_blacklist, skipping.")
            return

        # some APIs want the Koji build info, some others want the same
        # wrapped in a larger (request?) structure
        rbuildinfo = {
            'info': kbuildinfo,
            'nvr': kbuildinfo['nvr'].rsplit('-', 2),
        }

        with self.db_factory() as dbsession:
            rel = dbsession.query(Release).filter_by(
                create_automatic_updates=True, candidate_tag=btag).first()
            if not rel:
                log.debug(
                    f"Ignoring build being tagged into {btag!r}, no release configured for "
                    "automatic updates for it found.")
                return

            bcls = ContentType.infer_content_class(Build, kbuildinfo)
            build = bcls.get(bnvr)
            if build and build.update:
                log.info(
                    f"Build, active update for {bnvr} exists already, skipping."
                )
                return

            if not build:
                log.debug(f"Build for {bnvr} doesn't exist yet, creating.")

                # Package.get_or_create() infers content type already
                log.debug("Getting/creating related package object.")
                pkg = Package.get_or_create(dbsession, rbuildinfo)

                log.debug("Creating build object, adding it to the DB.")
                build = bcls(nvr=bnvr, package=pkg, release=rel)
                dbsession.add(build)

            owner_name = kbuildinfo['owner_name']
            user = User.get(owner_name)
            if not user:
                log.debug(f"Creating bodhi user for '{owner_name}'.")
                # Leave email, groups blank, these will be filled
                # in or updated when they log into Bodhi next time, see
                # bodhi.server.security:remember_me().
                user = User(name=owner_name)
                dbsession.add(user)

            log.debug(f"Creating new update for {bnvr}.")
            try:
                changelog = build.get_changelog(lastupdate=True)
            except ValueError:
                # Often due to bot-generated builds
                # https://github.com/fedora-infra/bodhi/issues/4146
                changelog = None
            except Exception:
                # Re-raise exception, so that the message can be re-queued
                raise
            closing_bugs = []
            if changelog:
                log.debug("Adding changelog to update notes.")
                notes = f"""Automatic update for {bnvr}.

##### **Changelog**

```
{changelog}
```"""

                if rel.name not in config.get('bz_exclude_rels'):
                    for b in re.finditer(config.get('bz_regex'), changelog,
                                         re.IGNORECASE):
                        idx = int(b.group(1))
                        log.debug(f'Adding bug #{idx} to the update.')
                        bug = Bug.get(idx)
                        if bug is None:
                            bug = Bug(bug_id=idx)
                            dbsession.add(bug)
                            dbsession.flush()
                        if bug not in closing_bugs:
                            closing_bugs.append(bug)
            else:
                notes = f"Automatic update for {bnvr}."
            update = Update(
                release=rel,
                builds=[build],
                bugs=closing_bugs,
                notes=notes,
                type=UpdateType.unspecified,
                stable_karma=3,
                unstable_karma=-3,
                autokarma=False,
                user=user,
                status=UpdateStatus.pending,
                critpath=Update.contains_critpath_component([build],
                                                            rel.branch),
            )

            # Comment on the update that it was automatically created.
            update.comment(
                dbsession,
                str("This update was automatically created"),
                author="bodhi",
            )

            update.add_tag(update.release.pending_signing_tag)

            log.debug("Adding new update to the database.")
            dbsession.add(update)

            log.debug("Flushing changes to the database.")
            dbsession.flush()

            # Obsolete older updates which may be stuck in testing due to failed gating
            try:
                update.obsolete_older_updates(dbsession)
            except Exception as e:
                log.error(f'Problem obsoleting older updates: {e}')

            alias = update.alias
            buglist = [b.bug_id for b in update.bugs]

        # This must be run after dbsession is closed so changes are committed to db
        work_on_bugs_task.delay(alias, buglist)
Пример #29
0
    def __call__(self, message: fedora_messaging.api.Message) -> None:
        """Create updates from appropriately tagged builds.

        Args:
            message: The message we are processing.
        """
        body = message.body

        missing = []
        for mandatory in ('tag', 'build_id', 'name', 'version', 'release'):
            if mandatory not in body:
                missing.append(mandatory)
        if missing:
            log.debug(
                f"Received incomplete tag message. Missing: {', '.join(missing)}"
            )
            return

        btag = body['tag']
        bnvr = '{name}-{version}-{release}'.format(**body)

        koji = buildsys.get_session()

        kbuildinfo = koji.getBuild(bnvr)
        if not kbuildinfo:
            log.debug(f"Can't find Koji build for {bnvr}.")
            return

        if 'nvr' not in kbuildinfo:
            log.debug(f"Koji build info for {bnvr} doesn't contain 'nvr'.")
            return

        if 'owner_name' not in kbuildinfo:
            log.debug(
                f"Koji build info for {bnvr} doesn't contain 'owner_name'.")
            return

        if kbuildinfo['owner_name'] in config.get(
                'automatic_updates_blacklist'):
            log.debug(
                f"{bnvr} owned by {kbuildinfo['owner_name']} who is listed in "
                "automatic_updates_blacklist, skipping.")
            return

        # some APIs want the Koji build info, some others want the same
        # wrapped in a larger (request?) structure
        rbuildinfo = {
            'info': kbuildinfo,
            'nvr': kbuildinfo['nvr'].rsplit('-', 2),
        }

        with self.db_factory() as dbsession:
            rel = dbsession.query(Release).filter_by(
                create_automatic_updates=True, candidate_tag=btag).first()
            if not rel:
                log.debug(
                    f"Ignoring build being tagged into {btag!r}, no release configured for "
                    "automatic updates for it found.")
                return

            bcls = ContentType.infer_content_class(Build, kbuildinfo)
            build = bcls.get(bnvr)
            if build and build.update:
                log.info(
                    f"Build, active update for {bnvr} exists already, skipping."
                )
                return

            if not build:
                log.debug(f"Build for {bnvr} doesn't exist yet, creating.")

                # Package.get_or_create() infers content type already
                log.debug("Getting/creating related package object.")
                pkg = Package.get_or_create(dbsession, rbuildinfo)

                log.debug("Creating build object, adding it to the DB.")
                build = bcls(nvr=bnvr, package=pkg, release=rel)
                dbsession.add(build)

            owner_name = kbuildinfo['owner_name']
            user = User.get(owner_name)
            if not user:
                log.debug(f"Creating bodhi user for '{owner_name}'.")
                # Leave email, groups blank, these will be filled
                # in or updated when they log into Bodhi next time, see
                # bodhi.server.security:remember_me().
                user = User(name=owner_name)
                dbsession.add(user)

            log.debug(f"Creating new update for {bnvr}.")
            changelog = build.get_changelog(lastupdate=True)
            if changelog:
                notes = f"""Automatic update for {bnvr}.

##### **Changelog**

```
{changelog}
```"""
            else:
                notes = f"Automatic update for {bnvr}."
            update = Update(
                release=rel,
                builds=[build],
                notes=notes,
                type=UpdateType.unspecified,
                stable_karma=3,
                unstable_karma=-3,
                autokarma=False,
                user=user,
                status=UpdateStatus.pending,
            )

            # Comment on the update that it was automatically created.
            update.comment(
                dbsession,
                str("This update was automatically created"),
                author="bodhi",
            )

            update.add_tag(update.release.pending_signing_tag)

            log.debug("Adding new update to the database.")
            dbsession.add(update)

            log.debug("Committing changes to the database.")
            dbsession.commit()