Example #1
0
    def test_list_comments_with_multiple_ignore_user(self):
        nvr = u'just-testing-1.0-2.fc17'
        another_user = User(name=u'aUser')
        self.db.add(another_user)
        update = Update(
            title=nvr,
            user=another_user,
            request=UpdateRequest.testing,
            type=UpdateType.enhancement,
            notes=u'Just another update.',
            date_submitted=datetime(1981, 10, 11),
            requirements=u'rpmlint',
            stable_karma=3,
            unstable_karma=-3,
        )
        update.release = Release.query.one()
        self.db.add(update)

        comment = Comment(karma=1, text=u'Cool! 😃')
        comment.user = another_user
        self.db.add(comment)
        update.comments.append(comment)
        self.db.flush()

        res = self.app.get('/comments/', {"ignore_user": "******"})
        body = res.json_body
        self.assertEqual(len(body['comments']), 1)
        self.assertNotIn('errors', body)
        self.assertEqual(body['comments'][0]['text'], u'Cool! 😃')
Example #2
0
    def run(self, api_version: int, data: dict):
        """
        Process the given message, updating relevant bugs and test cases.

        Duplicate messages: if the server delivers the message multiple times,
        the bugs and test cases are simply re-fetched and updated, so nothing
        bad happens.

        Args:
            api_version: API version number.
            data: Information about a new or edited update.
        """
        action = data["action"]
        alias = data['update'].get('alias')

        log.info("Updates Handler handling  %s, %s" % (alias, action))

        # Go to sleep for a second to try and avoid a race condition
        # https://github.com/fedora-infra/bodhi/issues/458
        time.sleep(1)

        with self.db_factory() as session:
            update = Update.get(alias)
            if not update:
                raise BodhiException("Couldn't find alias '%s' in DB" % alias)

            bugs = []
            if action == "edit":
                for idx in data['new_bugs']:
                    bug = Bug.get(idx)

                    # Sanity check
                    if bug is None or bug not in update.bugs:
                        update_bugs_ids = [b.bug_id for b in update.bugs]
                        update.update_bugs(update_bugs_ids + [idx], session)

                        # Now, after update.update_bugs, bug with idx should exists in DB
                        bug = Bug.get(idx)

                    bugs.append(bug)

            elif action == "testing":
                bugs = update.bugs
            else:
                raise NotImplementedError("Should never get here.")

            self.work_on_bugs(session, update, bugs)
            self.fetch_test_cases(session, update)

        if config['test_gating.required']:
            with self.db_factory() as session:
                update = Update.get(alias)
                update.update_test_gating_status()

        log.info("Updates Handler done with %s, %s" % (alias, action))
Example #3
0
    def __call__(self, message: typing.Union[UpdateEditV1,
                                             UpdateRequestTestingV1]):
        """
        Process the given message, updating relevant bugs and test cases.

        Duplicate messages: if the server delivers the message multiple times,
        the bugs and test cases are simply re-fetched and updated, so nothing
        bad happens.

        Args:
            message: A message about a new or edited update.
        """
        topic = message.topic
        alias = message.update.alias

        log.info("Updates Handler handling  %s, %s" % (alias, topic))

        # Go to sleep for a second to try and avoid a race condition
        # https://github.com/fedora-infra/bodhi/issues/458
        time.sleep(1)

        with self.db_factory() as session:
            update = Update.get(alias)
            if not update:
                raise BodhiException("Couldn't find alias '%s' in DB" % alias)

            bugs = []
            if isinstance(message, UpdateEditV1):
                for idx in message.new_bugs:
                    bug = Bug.get(idx)

                    # Sanity check
                    if bug is None or bug not in update.bugs:
                        update_bugs_ids = [b.bug_id for b in update.bugs]
                        update.update_bugs(update_bugs_ids + [idx], session)

                        # Now, after update.update_bugs, bug with idx should exists in DB
                        bug = Bug.get(idx)

                    bugs.append(bug)

            elif isinstance(message, UpdateRequestTestingV1):
                bugs = update.bugs
            else:
                raise NotImplementedError("Should never get here.")

            self.work_on_bugs(session, update, bugs)
            self.fetch_test_cases(session, update)

        if config['test_gating.required']:
            with self.db_factory() as session:
                update = Update.get(alias)
                update.update_test_gating_status()

        log.info("Updates Handler done with %s, %s" % (alias, topic))
Example #4
0
    def __call__(self, message: fedora_messaging.api.Message):
        """
        Process the given message, updating relevant bugs and test cases.

        Args:
            message: A message about a new or edited update.
        """
        msg = message.body['msg']
        topic = message.topic
        alias = msg['update'].get('alias')

        log.info("Updates Handler handling  %s, %s" % (alias, topic))

        # Go to sleep for a second to try and avoid a race condition
        # https://github.com/fedora-infra/bodhi/issues/458
        time.sleep(1)

        with self.db_factory() as session:
            update = Update.get(alias)
            if not update:
                raise BodhiException("Couldn't find alias '%s' in DB" % alias)

            bugs = []
            if topic.endswith('update.edit'):
                for idx in msg['new_bugs']:
                    bug = Bug.get(idx)

                    # Sanity check
                    if bug is None or bug not in update.bugs:
                        update_bugs_ids = [b.bug_id for b in update.bugs]
                        update.update_bugs(update_bugs_ids + [idx], session)

                        # Now, after update.update_bugs, bug with idx should exists in DB
                        bug = Bug.get(idx)

                    bugs.append(bug)

            elif topic.endswith('update.request.testing'):
                bugs = update.bugs
            else:
                raise NotImplementedError("Should never get here.")

            self.work_on_bugs(session, update, bugs)
            self.fetch_test_cases(session, update)

        if config['test_gating.required']:
            with self.db_factory() as session:
                update = Update.get(alias)
                update.update_test_gating_status()

        log.info("Updates Handler done with %s, %s" % (alias, topic))
Example #5
0
    def test_list_comments_by_multiple_usernames(self):
        update = Update(request=UpdateRequest.testing,
                        type=UpdateType.enhancement,
                        notes=u'Just another update.',
                        date_submitted=datetime(1981, 10, 11),
                        requirements=u'rpmlint',
                        stable_karma=3,
                        unstable_karma=-3,
                        release=Release.query.one())
        self.db.add(update)

        another_user = User(name=u'aUser')
        self.db.add(another_user)

        comment = Comment(karma=1, text=u'Cool! 😃')
        comment.user = another_user
        self.db.add(comment)
        update.comments.append(comment)
        self.db.flush()

        res = self.app.get('/comments/', {"user": "******"})
        body = res.json_body
        self.assertEqual(len(body['comments']), 2)
        self.assertEqual(body['comments'][0]['text'], u'Cool! 😃')
        self.assertEqual(body['comments'][1]['text'], u'wow. amaze.')
Example #6
0
    def setUp(self, *args, **kwargs):
        super(TestCommentsService, self).setUp(*args, **kwargs)

        # Add a second update owned by somebody else so we can test karma
        # policy stuff
        user2 = User(name='lmacken')
        self.db.flush()
        self.db.add(user2)
        release = self.db.query(Release).filter_by(name='F17').one()
        update = Update(
            user=user2,
            request=UpdateRequest.testing,
            type=UpdateType.enhancement,
            notes='Useful details!',
            release=release,
            date_submitted=datetime(1984, 11, 2),
            requirements='rpmlint',
            stable_karma=3,
            unstable_karma=-3,
        )
        self.db.add(update)
        build = RpmBuild(nvr=up2, update=update,
                         package=RpmPackage.query.filter_by(name='bodhi').one())
        self.db.add(build)
        self.db.flush()
Example #7
0
    def test_list_comments_by_multiple_update_owners(self):
        another_user = User(name='aUser')
        self.db.add(another_user)
        update = Update(
            user=another_user,
            request=UpdateRequest.testing,
            type=UpdateType.enhancement,
            notes='Just another update.',
            date_submitted=datetime(1981, 10, 11),
            requirements='rpmlint',
            stable_karma=3,
            unstable_karma=-3,
            release=Release.query.one()
        )
        self.db.add(update)

        comment = Comment(karma=1, text='Cool! 😃')
        comment.user = another_user
        self.db.add(comment)
        update.comments.append(comment)
        self.db.flush()

        res = self.app.get('/comments/', {"update_owner": "guest,aUser"})
        body = res.json_body
        self.assertEqual(len(body['comments']), 3)

        comment = body['comments'][0]
        self.assertEqual(comment['text'], 'Cool! 😃')
        comment = body['comments'][1]
        self.assertEqual(comment['text'], 'srsly.  pretty good.')
Example #8
0
    def test_list_comments_with_multiple_ignore_user(self):
        another_user = User(name='aUser')
        self.db.add(another_user)
        update = Update(user=another_user,
                        request=UpdateRequest.testing,
                        type=UpdateType.enhancement,
                        notes='Just another update.',
                        date_submitted=datetime(1981, 10, 11),
                        requirements='rpmlint',
                        stable_karma=3,
                        unstable_karma=-3,
                        release=Release.query.one())
        self.db.add(update)

        comment = Comment(karma=1, text='Cool! 😃')
        comment.user = another_user
        self.db.add(comment)
        update.comments.append(comment)
        self.db.flush()

        res = self.app.get('/comments/', {"ignore_user": "******"})
        body = res.json_body
        assert len(body['comments']) == 1
        assert 'errors' not in body
        assert body['comments'][0]['text'] == 'Cool! 😃'
Example #9
0
def create_update(session, build_nvrs, release_name=u'F17'):
    """
    Use the given session to create and return an Update with the given iterable of build_nvrs. Each
    build_nvr should be a string describing the name, version, and release for the build separated
    by dashes. For example, build_nvrs might look like this:

    (u'bodhi-2.3.3-1.fc24', u'python-fedora-atomic-composer-2016.3-1.fc24')

    You can optionally pass a release_name to select a different release than the default F17, but
    the release must already exist in the database.
    """
    release = session.query(Release).filter_by(name=release_name).one()
    user = session.query(User).filter_by(name=u'guest').one()

    builds = []
    for nvr in build_nvrs:
        name, version, rel = nvr.rsplit('-', 2)
        try:
            package = session.query(RpmPackage).filter_by(name=name).one()
        except sqlalchemy.orm.exc.NoResultFound:
            package = RpmPackage(name=name)
            session.add(package)
            user.packages.append(package)
            testcase = TestCase(name=u'Wat')
            session.add(testcase)
            package.test_cases.append(testcase)

        builds.append(RpmBuild(nvr=nvr, release=release, package=package, signed=True))
        session.add(builds[-1])

        # Add a buildroot override for this build
        expiration_date = datetime.utcnow()
        expiration_date = expiration_date + timedelta(days=1)
        override = BuildrootOverride(build=builds[-1], submitter=user,
                                     notes=u'blah blah blah',
                                     expiration_date=expiration_date)
        session.add(override)

    update = Update(
        title=', '.join(build_nvrs), builds=builds, user=user, request=UpdateRequest.testing,
        notes=u'Useful details!', type=UpdateType.bugfix, date_submitted=datetime(1984, 11, 2),
        requirements=u'rpmlint', stable_karma=3, unstable_karma=-3,
        test_gating_status=TestGatingStatus.passed)
    session.add(update)
    update.release = release

    return update
Example #10
0
    def consume(self, message):
        """
        Process the given message, updating relevant bugs and test cases.

        Args:
            message (munch.Munch): A fedmsg about a new or edited update.
        """
        msg = message['body']['msg']
        topic = message['topic']
        alias = msg['update'].get('alias')

        log.info("Updates Handler handling  %s, %s" % (alias, topic))

        # Go to sleep for a second to try and avoid a race condition
        # https://github.com/fedora-infra/bodhi/issues/458
        time.sleep(1)

        if not alias:
            log.error("Update Handler got update with no "
                      "alias %s." % pprint.pformat(msg))
            return

        with self.db_factory() as session:
            update = Update.get(alias)
            if not update:
                raise BodhiException("Couldn't find alias '%s' in DB" % alias)

            if topic.endswith('update.edit'):
                bugs = [Bug.get(idx) for idx in msg['new_bugs']]
                # Sanity check
                for bug in bugs:
                    assert bug in update.bugs
            elif topic.endswith('update.request.testing'):
                bugs = update.bugs
            else:
                raise NotImplementedError("Should never get here.")

            self.work_on_bugs(session, update, bugs)
            self.fetch_test_cases(session, update)

        if config['test_gating.required']:
            with self.db_factory() as session:
                update = Update.get(alias)
                update.update_test_gating_status()

        log.info("Updates Handler done with %s, %s" % (alias, topic))
Example #11
0
def main(alias: str, bugs: typing.List[int]):
    """
    Iterate the list of bugs, retrieving information from Bugzilla and modifying them.

    Iterate the given list of bugs associated with the given update. For each bug, retrieve
    details from Bugzilla, comment on the bug to let watchers know about the update, and mark
    the bug as MODIFIED. If the bug is a security issue, mark the update as a security update.

    Args:
        update: The update that the bugs are associated with.
        bugs: A list of bodhi.server.models.Bug instances that we wish to act on.
    """
    from bodhi.server.models import Bug, Update, UpdateType

    log.info(f'Got {len(bugs)} bugs to sync for {alias}')

    db_factory = util.transactional_session_maker()
    with db_factory() as session:
        update = Update.get(alias)
        if not update:
            raise BodhiException(f"Couldn't find alias {alias} in DB")

        for bug_id in bugs:
            bug = Bug.get(bug_id)
            # Sanity check
            if bug is None or bug not in update.bugs:
                update_bugs_ids = [b.bug_id for b in update.bugs]
                update.update_bugs(update_bugs_ids + [bug_id], session)
                # Now, after update.update_bugs, bug with bug_id should exists in DB
                bug = Bug.get(bug_id)

            log.info(f'Getting RHBZ bug {bug.bug_id}')
            try:
                rhbz_bug = bug_module.bugtracker.getbug(bug.bug_id)

                log.info(f'Updating our details for {bug.bug_id}')
                bug.update_details(rhbz_bug)
                log.info(f'  Got title {bug.title} for {bug.bug_id}')

                # If you set the type of your update to 'enhancement' but you
                # attach a security bug, we automatically change the type of your
                # update to 'security'. We need to do this first, so we don't
                # accidentally comment on stuff that we shouldn't.
                if not update.type == UpdateType.security and bug.security:
                    log.info("Setting our UpdateType to security.")
                    update.type = UpdateType.security

                log.info(f'Commenting on {bug.bug_id}')
                comment = config['initial_bug_msg'] % (
                    update.alias, update.release.long_name, update.abs_url())

                log.info(f'Modifying {bug.bug_id}')
                bug.modified(update, comment)
            except Exception:
                log.warning('Error occurred during updating single bug',
                            exc_info=True)
                raise ExternalCallException
Example #12
0
    def test_list_comments_by_update_no_comments(self):
        nvr = u'bodhi-2.0-201.fc17'
        update = Update(
            title=nvr,
            request=UpdateRequest.testing,
            type=UpdateType.enhancement,
            notes=u'Useful details!',
            date_submitted=datetime(1984, 11, 2),
            requirements=u'rpmlint',
            stable_karma=3,
            unstable_karma=-3,
        )
        update.release = Release.query.one()
        self.db.add(update)
        self.db.flush()

        res = self.app.get('/comments/', {"updates": nvr})
        body = res.json_body
        self.assertEquals(len(body['comments']), 0)
Example #13
0
def update_from_db_message(msgid: str, itemdict: dict):
    """
    Find and return update for waiverdb or resultsdb message.

    Used by the resultsdb and waiverdb consumers.

    Args:
        msgid:    the message ID (for logging purposes)
        itemdict: the relevant dict from the message. 'subject' dict
                  for a waiverdb message, 'item' dict for resultsdb.
    Returns:
        bodhi.server.models.Update or None: the relevant update, if
                                            found.
    """
    itemtype = itemdict.get("type")
    if not itemtype:
        log.error(f"Couldn't find item type in message {msgid}")
        return None
    if isinstance(itemtype, list):
        # In resultsdb.result.new messages, the values are all lists
        # for some reason
        itemtype = itemtype[0]
    if itemtype not in ("koji_build", "bodhi_update"):
        log.debug(f"Irrelevant item type {itemtype}")
        return None

    # find the update
    if itemtype == "bodhi_update":
        updateid = itemdict.get("item")
        if isinstance(updateid, list):
            updateid = updateid[0]
        if not updateid:
            log.error(f"Couldn't find update ID in message {msgid}")
            return None
        update = Update.get(updateid)
        if not update:
            log.error(f"Couldn't find update {updateid} in DB")
            return None
    else:
        nvr = itemdict.get("nvr", itemdict.get("item"))
        if isinstance(nvr, list):
            nvr = nvr[0]
        if not nvr:
            log.error(f"Couldn't find nvr in message {msgid}")
            return None
        build = Build.get(nvr)
        if not build:
            log.error(f"Couldn't find build {nvr} in DB")
            return None
        update = build.update

    return update
Example #14
0
    def test_list_comments_by_update_no_comments(self):
        update = Update(request=UpdateRequest.testing,
                        type=UpdateType.enhancement,
                        notes='Useful details!',
                        date_submitted=datetime(1984, 11, 2),
                        requirements='rpmlint',
                        stable_karma=3,
                        unstable_karma=-3,
                        release=Release.query.one())
        self.db.add(update)
        self.db.flush()

        res = self.app.get('/comments/', {"updates": update.alias})

        body = res.json_body
        assert len(body['comments']) == 0
Example #15
0
    def setup_method(self, method):
        """Set up environment for each test."""
        super().setup_method(method)

        release = self.db.query(Release).filter_by(name='F17').first()
        package = self.db.query(Package).first()
        user = self.db.query(User).first()

        self.update = Update(
            release=release,
            stable_karma=0,
            unstable_karma=0,
            notes='',
            type=UpdateType.bugfix,
            user=user,
        )
        self.build = Build(nvr='colord-1.3.4-1.fc26',
                           update=self.update,
                           type=package.type,
                           package=package)

        self.db.add(self.update)
        self.db.add(self.build)
        self.db.commit()

        body = {
            'contact': '*****@*****.**',
            'run': {
                'url': 'https://example.url',
            },
            'artifact': {
                'nvr': 'colord-1.3.4-1.fc26'
            },
            'pipeline': {
                'id': 442562,
            },
            'test': 'test',
            'generated_at': 'somedate',
            'version': '1.3.4',
        }

        self.sample_message = Message(topic='', body=body)

        self.db_factory = base.TransactionalSessionMaker(self.Session)
        self.handler = CIHandler(self.db_factory)
Example #16
0
        def _add_updates(updateslist, user, release, packagesuffix):
            """Private method that adds updates to the database for testing

            """
            count = 0
            for i in updateslist:
                for j in i[1]:
                    for k in range(0, j[1]):
                        update = Update(
                            user=user,
                            status=i[0],
                            type=j[0],
                            notes='Useful details!',
                            release=release,
                            date_submitted=datetime(1984, 11, 2),
                            requirements='rpmlint',
                            stable_karma=3,
                            unstable_karma=-3,
                        )
                        self.db.add(update)
                        self.db.commit()
                        count = count + 1
Example #17
0
def main(alias: str):
    """
    Query the wiki for test cases for each package on the given update.

    Args:
        alias: The update's builds are iterated upon to find test cases for
            them.
    """
    from bodhi.server.models import Update

    db_factory = transactional_session_maker()
    with db_factory() as session:
        update = Update.get(alias)
        if not update:
            raise BodhiException(f"Couldn't find alias {alias} in DB")

        for build in update.builds:
            try:
                build.update_test_cases(session)
            except ExternalCallException:
                log.warning('Error occurred during fetching testcases',
                            exc_info=True)
                raise ExternalCallException
Example #18
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().

    If the release is composed by Bodhi (i.e. a branched or stable release
    after the Bodhi activation point), ensure that related tags
    ``from_tag``-pending-signing and ``from_tag``-testing exists and if not
    create them in Koji. If the state of the release is not `pending`, add its
    pending-signing tag and remove it if it's a side tag.

    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)
    data.pop('sidetag_owner', 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.db, 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'])

        # Disable manual updates for releases not composed by Bodhi
        # see #4058
        if not from_tag:
            for release in releases:
                if not release.composed_by_bodhi:
                    request.errors.add(
                        'body', 'builds',
                        "Cannot manually create updates for a Release which is not "
                        "composed by Bodhi.\nRead the 'Automatic updates' page in "
                        "Bodhi docs about this error.")
                    request.db.rollback()
                    return

        # 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:
                for u in updates:
                    builds = [b.nvr for b in u.builds]
                    if not u.release.composed_by_bodhi:
                        # Before the Bodhi activation point of a release, keep builds tagged
                        # with the side-tag and its associate tags.
                        side_tag_signing_pending = u.release.get_pending_signing_side_tag(
                            from_tag)
                        side_tag_testing_pending = u.release.get_pending_testing_side_tag(
                            from_tag)
                        handle_side_and_related_tags_task.delay(
                            builds=builds,
                            pending_signing_tag=side_tag_signing_pending,
                            from_tag=from_tag,
                            pending_testing_tag=side_tag_testing_pending)
                    else:
                        # After the Bodhi activation point of a release, add the pending-signing tag
                        # of the release to funnel the builds back into a normal workflow for a
                        # stable release.
                        pending_signing_tag = u.release.pending_signing_tag
                        candidate_tag = u.release.candidate_tag
                        handle_side_and_related_tags_task.delay(
                            builds=builds,
                            pending_signing_tag=pending_signing_tag,
                            from_tag=from_tag,
                            candidate_tag=candidate_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
Example #19
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.

    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')

    caveats = []
    try:

        releases = set()
        builds = []

        # Create the Package and Build entities
        for nvr in data['builds']:
            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 data['builds']:
            # 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]
            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

                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)
    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
Example #20
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)
Example #21
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()
Example #22
0
    def run(self, api_version: int, data: dict):
        """
        Process the given message, updating relevant bugs and test cases.

        Duplicate messages: if the server delivers the message multiple times,
        the bugs and test cases are simply re-fetched and updated, so nothing
        bad happens.

        Args:
            api_version: API version number.
            data: Information about a new or edited update.
        """
        if api_version == 1:
            alias = data["update"].get("alias")
        elif api_version == 2:
            try:
                alias = data['update_alias']
            except KeyError:
                log.error(f"Wrong message format for the handle_update task: {data}")
                return
        else:
            log.error(f"The Updates Handler doesn't know how to handle api_version {api_version}. "
                      f"Message was: {data}")
            return

        action = data["action"]
        log.info("Updates Handler handling  %s, %s" % (alias, action))

        # Go to sleep for a second to try and avoid a race condition
        # https://github.com/fedora-infra/bodhi/issues/458
        time.sleep(1)

        with self.db_factory() as session:
            update = Update.get(alias)
            if not update:
                raise BodhiException("Couldn't find alias '%s' in DB" % alias)

            bugs = []
            if action == "edit":
                # If editing a Pending update, all of whose builds are signed, for a release
                # which isn't composed by Bodhi (i.e. Rawhide), move it directly to Testing.
                if not update.release.composed_by_bodhi \
                        and update.status == UpdateStatus.pending \
                        and update.signed:
                    log.info("Every build in the update is signed, set status to testing")

                    update.status = UpdateStatus.testing
                    update.date_testing = func.current_timestamp()
                    update.request = None

                    log.info(f"Update status of {update.display_name} has been set to testing")

                for idx in data['new_bugs']:
                    bug = Bug.get(idx)

                    # Sanity check
                    if bug is None or bug not in update.bugs:
                        update_bugs_ids = [b.bug_id for b in update.bugs]
                        update.update_bugs(update_bugs_ids + [idx], session)

                        # Now, after update.update_bugs, bug with idx should exists in DB
                        bug = Bug.get(idx)

                    bugs.append(bug)

            elif action == "testing":
                bugs = update.bugs
            else:
                raise NotImplementedError("Should never get here.")

            self.work_on_bugs(session, update, bugs)
            self.fetch_test_cases(session, update)

        if config['test_gating.required']:
            with self.db_factory() as session:
                update = Update.get(alias)
                update.update_test_gating_status()

        log.info("Updates Handler done with %s, %s" % (alias, action))