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