Exemple #1
0
def waive_test_results(request):
    """
    Waive all blocking test results on a given update when gating is on.

    Args:
        request (pyramid.request): The current request.
    Returns:
        dict: A dictionary mapping the key "update" to the update.
    """
    update = request.validated['update']
    comment = request.validated.pop('comment', None)
    tests = request.validated.pop('tests', None)

    try:
        update.waive_test_results(request.user.name, comment, tests)
    except LockedUpdateException as e:
        log.warning(str(e))
        request.errors.add('body', 'request', str(e))
    except BodhiException as e:
        log.error("Failed to waive the test results: %s", e)
        request.errors.add('body', 'request', str(e))
    except Exception as e:
        log.exception("Unhandled exception in waive_test_results")
        request.errors.add('body', 'request', str(e))

    return dict(update=update)
Exemple #2
0
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:
        tuple: A 3-tuple of the standard output (basestring), standard error (basestring), and the
            process's return code (int).
    """
    log.info('Running %r', cmd)
    if isinstance(cmd, six.string_types):
        cmd = cmd.split()
    p = subprocess.Popen(cmd, cwd=cwd,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    out, err = p.communicate()
    if out:
        log.debug(out)
    if err:
        if p.returncode == 0:
            log.debug(err)
        else:
            log.error(err)
    if p.returncode != 0:
        log.error('return code %s', p.returncode)
    return out, err, p.returncode
Exemple #3
0
def cmd(cmd, cwd=None, raise_on_error=False):
    """
    Run the given command in a subprocess.

    Args:
        cmd (list): The command to be run. This is expressed as a list to be
            passed directly to subprocess.Popen().
        cwd (str or None): The current working directory to use when launching the
            subprocess.
        raise_on_error (bool): If True, raise a RuntimeError if the command's exit code is non-0.
            Defaults to False.
    Returns:
        tuple: A 3-tuple of the standard output (str), standard error (str), and the
            process's return code (int).
    Raises:
        RuntimeError: If exception is True and the command's exit code is non-0.
    """
    log.debug('Running {}'.format(' '.join(cmd)))
    p = subprocess.Popen(cmd, cwd=cwd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = p.communicate()
    output = '{}\n{}'.format(out, err)
    if p.returncode != 0:
        msg = '{} returned a non-0 exit code: {}'.format(' '.join(cmd), p.returncode)
        log.error(msg)
        log.error(output)
        if raise_on_error:
            raise RuntimeError(msg)
    elif out or err:
        log.debug(f"subprocess output: {output}")
    return out, err, p.returncode
Exemple #4
0
def call_api(api_url, service_name, error_key=None, method='GET', data=None, headers=None):
    """
    Perform an HTTP request with response type and error handling.

    Args:
        api_url (basestring): The URL to query.
        service_name (basestring): The service name being queried (used to form human friendly error
            messages).
        error_key (basestring): The key that indexes error messages in the JSON body for the given
            service. If this is set to None, the JSON response will be used as the error message.
        method (basestring): The HTTP method to use for the request. Defaults to ``GET``.
        data (dict): Query string parameters that will be sent along with the request to the server.
    Returns:
        dict: A dictionary representing the JSON response from the remote service.
    Raises:
        RuntimeError: If the server did not give us a 200 code.
    """
    if data is None:
        data = dict()
    if method == 'POST':
        if headers is None:
            headers = {'Content-Type': 'application/json'}
        base_error_msg = (
            'Bodhi failed to send POST request to {0} at the following URL '
            '"{1}". The status code was "{2}".')
        rv = http_session.post(api_url,
                               headers=headers,
                               data=json.dumps(data),
                               timeout=60)
    else:
        base_error_msg = (
            'Bodhi failed to get a resource from {0} at the following URL '
            '"{1}". The status code was "{2}".')
        rv = http_session.get(api_url, timeout=60)
    if rv.status_code == 200:
        return rv.json()
    elif rv.status_code == 500:
        # There will be no JSON with an error message here
        error_msg = base_error_msg.format(
            service_name, api_url, rv.status_code)
        log.error(error_msg)
        raise RuntimeError(error_msg)
    else:
        # If it's not a 500 error, we can assume that the API returned an error
        # message in JSON that we can log
        try:
            rv_error = rv.json()
            if error_key is not None:
                rv_error = rv_error.get(error_key)
        except ValueError:
            rv_error = ''
        error_msg = base_error_msg.format(
            service_name, api_url, rv.status_code)
        error_msg = '{0} The error was "{1}".'.format(error_msg, rv_error)
        log.error(error_msg)
        raise RuntimeError(error_msg)
Exemple #5
0
    def work(testing, hide_existing, pkg=None, prefix=None):
        result = []
        koji.multicall = True

        releases = db.query(models.Release) \
                     .filter(
                         models.Release.state.in_(
                             (models.ReleaseState.pending,
                              models.ReleaseState.frozen,
                              models.ReleaseState.current)))

        kwargs = dict(package=pkg, prefix=prefix, latest=True)
        tag_release = dict()
        for release in releases:
            tag_release[release.candidate_tag] = release.long_name
            tag_release[release.testing_tag] = release.long_name
            tag_release[release.pending_testing_tag] = release.long_name
            tag_release[release.pending_signing_tag] = release.long_name
            koji.listTagged(release.candidate_tag, **kwargs)
            if testing:
                koji.listTagged(release.testing_tag, **kwargs)
                koji.listTagged(release.pending_testing_tag, **kwargs)
                koji.listTagged(release.pending_signing_tag, **kwargs)

        response = koji.multiCall() or []  # Protect against None
        for taglist in response:
            # if the call to koji results in errors, it returns them
            # in the reponse as dicts. Here we detect these, and log
            # the errors
            if isinstance(taglist, dict):
                log.error(taglist)
            else:
                for build in taglist[0]:
                    log.debug(build)
                    item = {
                        'nvr': build['nvr'],
                        'id': build['id'],
                        'package_name': build['package_name'],
                        'owner_name': build['owner_name'],
                        'release_name': tag_release[build['tag_name']]
                    }
                    # Prune duplicates
                    # https://github.com/fedora-infra/bodhi/issues/450

                    if item not in result:
                        if hide_existing:
                            # show only builds that don't have updates already
                            b = request.db.query(models.Build).filter_by(nvr=build['nvr']).first()
                            if (b and b.update is None) or not b:
                                result.append(item)
                        else:
                            result.append(item)
        return result
Exemple #6
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.release.state is ReleaseState.archived:
        request.errors.add('body', 'request',
                           "Can't change request for an archived release")
        return

    if action == UpdateRequest.stable:
        settings = request.registry.settings
        result, reason = update.check_requirements(request.db, settings)
        log.info(
            f'Unable to set request for {update.alias} to {action} due to failed requirements: '
            f'{reason}')
        if not result:
            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.error("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)
Exemple #7
0
def trigger_tests(request):
    """
    Trigger tests for update.

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

    if update.status != UpdateStatus.testing:
        log.error("Can't trigger tests for update: Update is not in testing status")
        request.errors.add('body', 'request', 'Update is not in testing status')
    else:
        message = update_schemas.UpdateReadyForTestingV1.from_dict(
            message={'update': update, 'agent': 'bodhi', 're-trigger': True}
        )
        notifications.publish(message)

    return dict(update=update)
Exemple #8
0
def get_test_results(request):
    """
    Get the test results on a given update when gating is on.

    Args:
        request (pyramid.request): The current request.
    Returns:
        dict: A dictionary mapping the key 'decisions' to a list of result dictionaries.
    """
    update = request.validated['update']

    decisions = []
    try:
        decisions = update.get_test_gating_info()
    except RequestsTimeout as e:
        log.error("Error querying greenwave for test results - timed out")
        request.errors.add('body', 'request', str(e))
        request.errors.status = 504
    except (RequestException, RuntimeError) as e:
        log.error("Error querying greenwave for test results: %s", e)
        request.errors.add('body', 'request', str(e))
        request.errors.status = 502
    except BodhiException as e:
        log.error("Failed to query greenwave for test results: %s", e)
        request.errors.add('body', 'request', str(e))
        request.errors.status = 501
    except Exception as e:
        log.exception("Unhandled exception in get_test_results")
        request.errors.add('body', 'request', str(e))
        request.errors.status = 500

    return dict(decisions=decisions)
Exemple #9
0
def trigger_tests(request):
    """
    Trigger tests for update.

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

    if update.status != UpdateStatus.testing:
        log.error("Can't trigger tests for update: Update is not in testing status")
        request.errors.add('body', 'request', 'Update is not in testing status')
    else:
        if update.content_type == ContentType.rpm:
            message = update_schemas.UpdateReadyForTestingV1.from_dict(
                message=update._build_group_test_message()
            )
            notifications.publish(message)

    return dict(update=update)
Exemple #10
0
def read_template(name):
    """
    Read template text from file.

    Args:
        name (basestring): The name of the email template stored in 'release' table in database.
    Returns:
        basestring: The text read from the file.
    """
    location = config.get('mail.templates_basepath')
    directory = get_absolute_path(location)
    file_name = "%s.tpl" % (name)
    template_path = os.path.join(directory, file_name)

    if os.path.exists(template_path):
        try:
            with open(template_path) as template_file:
                return to_unicode(template_file.read())
        except IOError as e:
            log.error("Unable to read template file: %s" % (template_path))
            log.error("IO Error[%s]: %s" % (e.errno, e.strerror))
    else:
        log.error("Path does not exist: %s" % (template_path))
Exemple #11
0
    def work(testing, hide_existing, pkg=None, prefix=None):
        result = []
        koji.multicall = True

        releases = db.query(models.Release) \
                     .filter(
                         models.Release.state.in_(
                             (models.ReleaseState.pending,
                              models.ReleaseState.frozen,
                              models.ReleaseState.current)))

        if hide_existing:
            # We want to filter out builds associated with an update.
            # Since the candidate_tag is removed when an update is pushed to
            # stable, we only need a list of builds that are associated to
            # updates still in pending state.

            # Don't filter by releases here, because the associated update
            # might be archived but the build might be inherited into an active
            # release. If this gives performance troubles later on, caching
            # this set should be easy enough.
            associated_build_nvrs = set(
                row[0] for row in
                db.query(models.Build.nvr).
                join(models.Update).
                filter(models.Update.status == models.UpdateStatus.pending)
            )

        kwargs = dict(package=pkg, prefix=prefix, latest=True)
        tag_release = dict()
        for release in releases:
            tag_release[release.candidate_tag] = release.long_name
            tag_release[release.testing_tag] = release.long_name
            tag_release[release.pending_testing_tag] = release.long_name
            tag_release[release.pending_signing_tag] = release.long_name
            koji.listTagged(release.candidate_tag, **kwargs)
            if testing:
                koji.listTagged(release.testing_tag, **kwargs)
                koji.listTagged(release.pending_testing_tag, **kwargs)
                if release.pending_signing_tag:
                    koji.listTagged(release.pending_signing_tag, **kwargs)

        response = koji.multiCall() or []  # Protect against None
        for taglist in response:
            # if the call to koji results in errors, it returns them
            # in the reponse as dicts. Here we detect these, and log
            # the errors
            if isinstance(taglist, dict):
                log.error('latest_candidates endpoint asked Koji about a non-existent tag:')
                log.error(taglist)
            else:
                for build in taglist[0]:
                    if hide_existing and build['nvr'] in associated_build_nvrs:
                        continue

                    item = {
                        'nvr': build['nvr'],
                        'id': build['id'],
                        'package_name': build['package_name'],
                        'owner_name': build['owner_name'],
                    }

                    # The build's tag might not be present in tag_release
                    # because its associated release is archived and therefore
                    # filtered out in the query above.
                    if build['tag_name'] in tag_release:
                        item['release_name'] = tag_release[build['tag_name']]

                    # Prune duplicates
                    # https://github.com/fedora-infra/bodhi/issues/450
                    if item not in result:
                        result.append(item)
        return result