Exemplo n.º 1
0
def admin(request):
    """
    Return a dictionary with keys "user" and "principals".

    "user" indexes the current user's name, and "principals" indexes the user's effective
    principals.

    Args:
        request (pyramid.request): The current request.
    Returns:
        dict: A dictionary as described above.
    """
    user = request.user
    log.info('%s logged into admin panel' % user.name)
    principals = request.effective_principals
    return {'user': user.name, 'principals': principals}
Exemplo n.º 2
0
def create_or_update_user(db, username, email, groups):
    """Create or update a user in the database.

    Args:
        db (sqlalchemy.orm.session.Session): The database session.
        username (str): The username to create or update
        email (str): The user's email address
        groups (list(str)): A list of group names the user belongs to, that will be synced.

    Returns:
        bodhi.server.models.User: The user instance.
    """
    # Find the user in our database. Create it if it doesn't exist.
    user = db.query(User).filter_by(name=username).first()
    if not user:
        user = User(name=username, email=email)
        db.add(user)
        db.flush()
    else:
        # Update email address if the address changed
        if user.email != email:
            user.email = email
            db.flush()

    # Keep track of what groups the user is a member of
    for group_name in groups:
        # Drop empty group names https://github.com/fedora-infra/bodhi/issues/306
        if not group_name.strip():
            continue

        group = db.query(Group).filter_by(name=group_name).first()
        if not group:
            group = Group(name=group_name)
            db.add(group)
            db.flush()
        if group not in user.groups:
            log.info('Adding %s to %s group', user.name, group.name)
            user.groups.append(group)

    # See if the user was removed from any groups
    for group in user.groups:
        if group.name not in groups:
            log.info('Removing %s from %s group', user.name, group.name)
            user.groups.remove(group)

    return user
Exemplo n.º 3
0
def set_request(request):
    """
    Set a specific :class:`bodhi.server.models.UpdateRequest` on a given update.

    Args:
        request (pyramid.request): The current request.
    Returns:
        dict: A dictionary mapping the key "update" to the update that was modified.
    """
    update = request.validated['update']
    action = request.validated['request']

    if update.locked:
        request.errors.add('body', 'request',
                           "Can't change request on a locked update")
        return

    if update.status == UpdateStatus.stable and action == UpdateRequest.testing:
        request.errors.add(
            'body', 'request',
            "Pushing back to testing a stable update is not allowed")
        return

    if action == UpdateRequest.stable:
        settings = request.registry.settings
        result, reason = update.check_requirements(request.db, settings)
        if not result:
            log.info(
                f'Unable to set request for {update.alias} to stable due to failed requirements: '
                f'{reason}')
            request.errors.add('body', 'request',
                               'Requirement not met %s' % reason)
            return

    try:
        update.set_request(request.db, action, request.user.name)
    except BodhiException as e:
        log.info("Failed to set the request: %s", e)
        request.errors.add('body', 'request', str(e))
    except Exception as e:
        log.exception("Unhandled exception in set_request")
        request.errors.add('body', 'request', str(e))

    return dict(update=update)
Exemplo n.º 4
0
def sorted_updates(updates):
    """
    Sort the given iterable of Updates so the highest version appears last.

    Order our updates so that the highest version gets tagged last so that
    it appears as the 'latest' in koji.

    Args:
        updates (iterable): An iterable of bodhi.server.models.Update objects to be sorted.
    Returns:
        tuple: A 2-tuple of lists. The first list contains builds that should be tagged
            synchronously in a specific order. The second list can be tagged asynchronously in koji
            with a multicall.
    """
    builds = defaultdict(set)
    sync, async_ = [], []
    for update in updates:
        for build in update.builds:
            builds[build.nvr_name].add(build)
    # The sorted here is so we actually have a way to test this
    # Otherwise, we would be depending on the way Python orders dict keys
    for package in sorted(builds.keys()):
        if len(builds[package]) > 1:
            log.debug(builds[package])
            for build in sorted_builds(builds[package])[::-1]:
                if build.update not in sync:
                    sync.append(build.update)
                if build.update in async_:
                    async_.remove(build.update)
        else:
            build = list(builds[package])[0]
            if build.update not in async_ and build.update not in sync:
                async_.append(build.update)
    log.info('sync = %s', [up.alias for up in sync])
    log.info('async_ = %s', [up.alias for up in async_])
    if not (len(set(sync) & set(async_)) == 0
            and len(set(sync) | set(async_)) == len(updates)):
        # There should be absolutely no way to hit this code path, but let's be paranoid, and check
        # every run, to make sure no update gets left behind.
        # It makes sure that there is no update in sync AND async, and that the combination of
        # sync OR async_ is the full set of updates.
        raise Exception('ERROR! SYNC+ASYNC != UPDATES! sorted_updates failed'
                        )  # pragma: no cover
    return sync, async_
Exemplo n.º 5
0
def save_release(request):
    """
    Save a release.

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

    Args:
        request (pyramid.request): The current request.
    Returns:
        bodhi.server.models.Request: The created or edited Request.
    """
    data = request.validated

    edited = data.pop("edited", None)

    # 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', None)

    try:
        if edited is None:
            log.info("Creating a new release: %s" % data['name'])
            r = Release(**data)

        else:
            log.info("Editing release: %s" % edited)
            r = request.db.query(Release).filter(Release.name == edited).one()
            for k, v in data.items():
                setattr(r, k, v)

    except Exception as e:
        log.exception(e)
        request.errors.add('body', 'release',
                           'Unable to create update: %s' % e)
        return

    request.db.add(r)
    request.db.flush()

    return r
Exemplo n.º 6
0
def remember_me(context: 'mako.runtime.Context', request: 'pyramid.request.Request',
                info: dict, *args, **kw) -> HTTPFound:
    """
    Remember information about a newly logged in user given by the OpenID provider.

    This is configured via the openid.success_callback configuration, and is called upon successful
    login.

    Args:
        context: The current template rendering context. Unused.
        request: The current request.
        info: The information passed to Bodhi from the OpenID provider about the
            authenticated user. This includes things like the user's username, e-mail address and
            groups.
        args: A list of additional positional parameters. Unused.
        kw: A dictionary of additional keyword parameters. Unused.
    Returns:
        A 302 redirect to the URL the user was visiting before
            they clicked login, or home if they have not used a valid OpenID provider.
    """
    log.debug('remember_me(%s)' % locals())
    log.debug('remember_me: request.params = %r' % request.params)
    endpoint = request.params['openid.op_endpoint']
    if endpoint != request.registry.settings['openid.provider']:
        log.warning('Invalid OpenID provider: %s' % endpoint)
        raise HTTPUnauthorized(
            'Invalid OpenID provider. You can only use: %s' %
            request.registry.settings['openid.provider']
        )

    username = info['sreg']['nickname']
    email = info['sreg']['email']
    log.debug('remember_me: groups = %s' % info['groups'])
    log.info('%s successfully logged in' % username)

    create_or_update_user(request.db, username, email, info["groups"])

    headers = remember(request, username)

    response = get_final_redirect(request)
    response.headerlist.extend(headers)
    return response
Exemplo n.º 7
0
    def __init__(self, hub, db_factory=None, mash_dir=config.get('mash_dir'),
                 *args, **kw):
        if not db_factory:
            config_uri = '/etc/bodhi/production.ini'
            settings = get_appsettings(config_uri)
            engine = engine_from_config(settings, 'sqlalchemy.')
            Base.metadata.create_all(engine)
            self.db_factory = transactional_session_maker()
        else:
            self.db_factory = db_factory

        buildsys.setup_buildsystem(config)
        bugs.set_bugtracker()
        self.mash_dir = mash_dir
        prefix = hub.config.get('topic_prefix')
        env = hub.config.get('environment')
        self.topic = prefix + '.' + env + '.' + hub.config.get('masher_topic')
        self.valid_signer = hub.config.get('releng_fedmsg_certname')
        if not self.valid_signer:
            log.warn('No releng_fedmsg_certname defined'
                     'Cert validation disabled')
        super(Masher, self).__init__(hub, *args, **kw)
        log.info('Bodhi masher listening on topic: %s' % self.topic)
Exemplo n.º 8
0
def get_and_store_user(
    request: 'pyramid.request.Request',
    access_token: str,
    response: 'pyramid.response.Response'
):
    """Get or create the user and log them in.

    Get additional information about the user from the OIDC provider, create it
    if it doesn't exist yet, sync the groups, and log them in.

    Args:
        request (pyramid.request.Request): The Pyramid request.
        access_token (str): A valid access token.
        response (pyramid.response.Response): The Pyramid response to add the session
            cookie headers to.

    Returns:
        bodhi.models.User: The user instance.
    """
    # Get information about the user
    userinfo = request.registry.oidc.fedora.userinfo(token={"access_token": access_token})
    if "error" in userinfo:
        raise InvalidTokenError(description=userinfo["error_description"])

    username = userinfo['nickname']
    log.info(f'{username} successfully logged in')
    # Create or update the user in the database, update the groups
    user = create_or_update_user(
        request.db,
        username,
        userinfo['email'],
        userinfo.get("groups", []),
    )
    # Log the user in
    headers = remember(request, username)
    response.headerlist.extend(headers)
    return user
Exemplo n.º 9
0
def send_mail(from_addr, to_addr, subject, body_text, headers=None):
    """
    Send an e-mail.

    Args:
        from_addr (basestring): The address to use in the From: header.
        to_addr (basestring): The address to send the e-mail to.
        subject (basestring): The subject of the e-mail.
        body_text (basestring): The body of the e-mail to be sent.
        headers (dict or None): A mapping of header fields to values to be included in the e-mail,
            if not None.
    """
    if not from_addr:
        from_addr = config.get('bodhi_email')
    if not from_addr:
        log.warning(
            'Unable to send mail: bodhi_email not defined in the config')
        return
    if to_addr in config.get('exclude_mail'):
        return

    from_addr = to_bytes(from_addr)
    to_addr = to_bytes(to_addr)
    subject = to_bytes(subject)
    body_text = to_bytes(body_text)

    msg = [b'From: %s' % from_addr, b'To: %s' % to_addr]
    if headers:
        for key, value in headers.items():
            msg.append(b'%s: %s' % (to_bytes(key), to_bytes(value)))
    msg.append(b'X-Bodhi: %s' % to_bytes(config.get('default_email_domain')))
    msg += [b'Subject: %s' % subject, b'', body_text]
    body = b'\r\n'.join(msg)

    log.info('Sending mail to %s: %s', to_addr, subject)
    _send_mail(from_addr, to_addr, body)
Exemplo n.º 10
0
Arquivo: util.py Projeto: hanzz/bodhi
def sorted_updates(updates):
    """
    Sort the given iterable of Updates so the highest version appears last.

    Order our updates so that the highest version gets tagged last so that
    it appears as the 'latest' in koji.

    Args:
        updates (iterable): An iterable of bodhi.server.models.Update objects to be sorted.
    Returns:
        tuple: A 2-tuple of lists. The first list contains builds that should be tagged
            synchronously in a specific order. The second list can be tagged asynchronously in koji
            with a multicall.
    """
    builds = defaultdict(set)
    build_to_update = {}
    sync, async = [], []
    for update in updates:
        for build in update.builds:
            n, v, r = get_nvr(build.nvr)
            builds[n].add(build.nvr)
            build_to_update[build.nvr] = update
    for package in builds:
        if len(builds[package]) > 1:
            log.info('Found multiple %s packages' % package)
            log.debug(builds[package])
            for build in sorted_builds(builds[package])[::-1]:
                update = build_to_update[build]
                if update not in sync:
                    sync.append(update)
                if update in async:
                    async.remove(update)
        else:
            update = build_to_update[builds[package].pop()]
            if update not in async and update not in sync:
                async.append(update)
Exemplo n.º 11
0
def save_stack(request):
    """
    Save a stack.

    Args:
        request (pyramid.request): The current web request.
    Returns:
        dict: A dictionary with key "stack" that indexes the newly created Stack.
    """
    data = request.validated
    db = request.db
    user = User.get(request.user.name)

    # Fetch or create the stack
    stack = Stack.get(data['name'])
    if not stack:
        stack = Stack(name=data['name'], users=[user])
        db.add(stack)
        db.flush()

    if stack.users or stack.groups:
        if user in stack.users:
            log.info('%s is an owner of the %s', user.name, stack.name)
        else:
            for group in user.groups:
                if group in stack.groups:
                    log.info('%s is a member of the %s group', user.name, stack.name)
                    break
            else:
                log.warning('%s is not an owner of the %s stack',
                            user.name, stack.name)
                log.debug('owners = %s; groups = %s', stack.users, stack.groups)
                request.errors.add('body', 'name', '%s does not have privileges'
                                   ' to modify the %s stack' % (user.name, stack.name))
                request.errors.status = HTTPForbidden.code
                return

    # Update the stack description
    desc = data['description']
    if desc:
        stack.description = desc

    # Update the stack requirements
    # If the user passed in no value at all for requirements, then use
    # the site defaults.  If, however, the user passed in the empty string, we
    # assume they mean *really*, no requirements so we leave the value null.
    reqs = data['requirements']
    if reqs is None:
        stack.requirements = config.get('site_requirements')
    elif reqs:
        stack.requirements = reqs

    stack.update_relationship('users', User, data, db)
    stack.update_relationship('groups', Group, data, db)

    # We make a special case out of packages here, since when a package is
    # added to a stack, we want to give it the same requirements as the stack
    # has. See https://github.com/fedora-infra/bodhi/issues/101
    new, same, rem = stack.update_relationship('packages', Package, data, db)
    if stack.requirements:
        additional = list(tokenize(stack.requirements))

        for name in new:
            package = Package.get(name)
            original = package.requirements
            original = [] if not original else list(tokenize(original))
            package.requirements = " ".join(list(set(original + additional)))

    log.info('Saved %s stack', data['name'])
    notifications.publish(topic='stack.save', msg=dict(
        stack=stack, agent=user.name))

    return dict(stack=stack)
Exemplo n.º 12
0
def save_override(request):
    """
    Create or edit a buildroot override.

    This entails either creating a new buildroot override, or editing an
    existing one. To edit an existing buildroot override, the buildroot
    override's original id needs to be specified in the ``edited`` parameter.

    Args:
        request (pyramid.request): The current web request.
    Returns:
        dict: The new or edited override.
    """
    data = request.validated

    edited = data.pop("edited")

    caveats = []
    try:
        submitter = User.get(request.user.name)
        if edited is None:
            builds = data['builds']
            overrides = []
            if len(builds) > 1:
                caveats.append({
                    'name':
                    'nvrs',
                    'description':
                    'Your override submission was '
                    'split into %i.' % len(builds)
                })
            for build in builds:
                log.info("Creating a new buildroot override: %s" % build.nvr)
                existing_override = BuildrootOverride.get(build.id)
                if existing_override:
                    if not existing_override.expired_date:
                        data['expiration_date'] = max(
                            existing_override.expiration_date,
                            data['expiration_date'])

                    new_notes = f"""{data['notes']}
_____________
_@{existing_override.submitter.name} ({existing_override.submission_date.strftime('%b %d, %Y')})_
{existing_override.notes}"""
                    # Truncate notes at 2000 chars
                    if len(new_notes) > 2000:
                        new_notes = new_notes[:1972] + '(...)\n___Notes truncated___'

                    overrides.append(
                        BuildrootOverride.edit(
                            request,
                            edited=build,
                            submitter=submitter,
                            submission_date=datetime.now(),
                            notes=new_notes,
                            expiration_date=data['expiration_date'],
                            expired=None,
                        ))
                else:
                    overrides.append(
                        BuildrootOverride.new(
                            request,
                            build=build,
                            submitter=submitter,
                            notes=data['notes'],
                            expiration_date=data['expiration_date'],
                        ))

            if len(builds) > 1:
                result = dict(overrides=overrides)
            else:
                result = overrides[0]
        else:
            log.info("Editing buildroot override: %s" % edited)

            edited = Build.get(edited)

            if edited is None:
                request.errors.add('body', 'edited', 'No such build')
                return

            result = BuildrootOverride.edit(
                request,
                edited=edited,
                submitter=submitter,
                notes=data["notes"],
                expired=data["expired"],
                expiration_date=data["expiration_date"])

            if not result:
                # Some error inside .edit(...)
                return

    except Exception as e:
        log.exception(e)
        request.errors.add('body', 'override',
                           'Unable to save buildroot override: %s' % e)
        return

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

    result['caveats'] = caveats

    return result
Exemplo n.º 13
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
Exemplo n.º 14
0
def save_override(request):
    """
    Create or edit a buildroot override.

    This entails either creating a new buildroot override, or editing an
    existing one. To edit an existing buildroot override, the buildroot
    override's original id needs to be specified in the ``edited`` parameter.

    Args:
        request (pyramid.request): The current web request.
    Returns:
        dict: The new or edited override.
    """
    data = request.validated

    edited = data.pop("edited")

    caveats = []
    try:
        submitter = User.get(request.user.name)
        if edited is None:
            builds = data['builds']
            overrides = []
            if len(builds) > 1:
                caveats.append({
                    'name': 'nvrs',
                    'description': 'Your override submission was '
                    'split into %i.' % len(builds)
                })
            for build in builds:
                log.info("Creating a new buildroot override: %s" % build.nvr)
                if BuildrootOverride.get(build.id):
                    request.errors.add('body', 'builds',
                                       'Buildroot override for %s already exists' % build.nvr)
                    return
                else:
                    overrides.append(BuildrootOverride.new(
                        request,
                        build=build,
                        submitter=submitter,
                        notes=data['notes'],
                        expiration_date=data['expiration_date'],
                    ))

            if len(builds) > 1:
                result = dict(overrides=overrides)
            else:
                result = overrides[0]
        else:
            log.info("Editing buildroot override: %s" % edited)

            edited = Build.get(edited)

            if edited is None:
                request.errors.add('body', 'edited', 'No such build')
                return

            result = BuildrootOverride.edit(
                request, edited=edited, submitter=submitter,
                notes=data["notes"], expired=data["expired"],
                expiration_date=data["expiration_date"])

            if not result:
                # Some error inside .edit(...)
                return

    except Exception as e:
        log.exception(e)
        request.errors.add('body', 'override',
                           'Unable to save buildroot override: %s' % e)
        return

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

    result['caveats'] = caveats

    return result
Exemplo n.º 15
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
Exemplo n.º 16
0
def save_release(request):
    """
    Save a release.

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

    Args:
        request (pyramid.request): The current request.
    Returns:
        bodhi.server.models.Request: The created or edited Request.
    """
    data = request.validated

    edited = data.pop("edited", None)

    # 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', None)

    try:
        if edited is None:
            log.info("Creating a new release: %s" % data['name'])
            r = Release(**data)

        else:
            log.info("Editing release: %s" % edited)
            r = request.db.query(Release).filter(Release.name == edited).one()
            for k, v in data.items():
                # We have to change updates status to obsolete
                # if state of release changes to archived
                if k == "state" and v == ReleaseState.archived and \
                        r.state != ReleaseState.archived:
                    updates = request.db.query(Update).filter(Update.release_id == r.id).filter(
                        Update.status.notin_(
                            [UpdateStatus.obsolete, UpdateStatus.stable, UpdateStatus.unpushed]
                        )
                    ).all()
                    for u in updates:
                        u.status = UpdateStatus.obsolete
                        u.comment(
                            request.db,
                            'This update is marked obsolete because '
                            'the {} release is archived.'.format(u.release.name),
                            author='bodhi',
                        )
                # Inform user that update requested for stable
                # will be pushed to stable after the freeze is over.
                if k == "state" and v == ReleaseState.frozen and \
                        r.state != ReleaseState.frozen:
                    updates = request.db.query(Update).filter(Update.release_id == r.id).filter(
                        Update.request == UpdateRequest.stable
                    ).filter(
                        Update.locked == False
                    ).all()
                    for u in updates:
                        u.comment(
                            request.db,
                            'There is an ongoing freeze; this will be pushed to'
                            ' stable after the freeze is over.',
                            author='bodhi',
                        )
                setattr(r, k, v)

        # We have to invalidate the release cache after change
        Release.clear_all_releases_cache()

    except Exception as e:
        log.exception(e)
        request.errors.add('body', 'release',
                           'Unable to create/edit release: %s' % e)
        return

    request.db.add(r)
    request.db.flush()

    return r
Exemplo n.º 17
0
Arquivo: util.py Projeto: hanzz/bodhi
            build_to_update[build.nvr] = update
    for package in builds:
        if len(builds[package]) > 1:
            log.info('Found multiple %s packages' % package)
            log.debug(builds[package])
            for build in sorted_builds(builds[package])[::-1]:
                update = build_to_update[build]
                if update not in sync:
                    sync.append(update)
                if update in async:
                    async.remove(update)
        else:
            update = build_to_update[builds[package].pop()]
            if update not in async and update not in sync:
                async.append(update)
    log.info('sync = %s' % ([up.title for up in sync],))
    log.info('async = %s' % ([up.title for up in async],))
    return sync, async


def cmd(cmd, cwd=None):
    """
    Run the given command in a subprocess.

    Args:
        cmd (list or basestring): The command to be run. This may be expressed as a list to be
            passed directly to subprocess.Popen(), or as a basestring which will be processed with
            basestring.split() to form the list to pass to Popen().
        cwd (basestring or None): The current working directory to use when launching the
            subprocess.
    Returns: