def rm_operators(): """ Submit a request to remove operators from an index image. :rtype: flask.Response :raise ValidationError: if required parameters are not supplied """ payload = flask.request.get_json() if not isinstance(payload, dict): raise ValidationError('The input data must be a JSON object') request = RequestRm.from_json(payload) db.session.add(request) db.session.commit() messaging.send_message_for_state_change(request, new_batch_msg=True) overwrite_from_index = payload.get('overwrite_from_index') args = _get_rm_args(payload, request, overwrite_from_index) safe_args = _get_safe_args(args, payload) error_callback = failed_request_callback.s(request.id) try: handle_rm_request.apply_async( args=args, link_error=error_callback, argsrepr=repr(safe_args), queue=_get_user_queue(serial=overwrite_from_index), ) except kombu.exceptions.OperationalError: handle_broker_error(request) flask.current_app.logger.debug('Successfully scheduled request %d', request.id) return flask.jsonify(request.to_json()), 201
def regenerate_bundle(): """ Submit a request to regenerate an operator bundle image. :rtype: flask.Response :raise ValidationError: if required parameters are not supplied """ payload = flask.request.get_json() if not isinstance(payload, dict): raise ValidationError('The input data must be a JSON object') request = RequestRegenerateBundle.from_json(payload) db.session.add(request) db.session.commit() messaging.send_message_for_state_change(request, new_batch_msg=True) error_callback = failed_request_callback.s(request.id) handle_regenerate_bundle_request.apply_async( args=[ payload['from_bundle_image'], payload.get('organization'), request.id ], link_error=error_callback, queue=_get_user_queue(), ) flask.current_app.logger.debug('Successfully scheduled request %d', request.id) return flask.jsonify(request.to_json()), 201
def test_send_message_for_state_change( mock_sm, mock_gbsce, mock_grstce, batch_msg_expected, request_msg_expected, app, db, minimal_request_add, ): expected_msgs = [] if request_msg_expected: request_envelope = mock.Mock() expected_msgs.append(request_envelope) mock_grstce.return_value = request_envelope else: mock_grstce.return_value = None if batch_msg_expected: batch_envelope = mock.Mock() expected_msgs.append(batch_envelope) mock_gbsce.return_value = batch_envelope else: mock_gbsce.return_value = None messaging.send_message_for_state_change(minimal_request_add) if expected_msgs: mock_sm.assert_called_once_with(expected_msgs) else: mock_sm.assert_not_called()
def add_bundles(): """ Submit a request to add operator bundles to an index image. :rtype: flask.Response :raise ValidationError: if required parameters are not supplied """ payload = flask.request.get_json() if not isinstance(payload, dict): raise ValidationError('The input data must be a JSON object') request = RequestAdd.from_json(payload) db.session.add(request) db.session.commit() messaging.send_message_for_state_change(request, new_batch_msg=True) overwrite_from_index = _should_force_overwrite() or payload.get( 'overwrite_from_index') celery_queue = _get_user_queue(serial=overwrite_from_index) args = [ payload['bundles'], payload['binary_image'], request.id, payload.get('from_index'), payload.get('add_arches'), payload.get('cnr_token'), payload.get('organization'), payload.get('force_backport'), overwrite_from_index, payload.get('overwrite_from_index_token'), flask.current_app.config['IIB_GREENWAVE_CONFIG'].get(celery_queue), ] safe_args = copy.copy(args) if payload.get('cnr_token'): safe_args[safe_args.index(payload['cnr_token'])] = '*****' if payload.get('overwrite_from_index_token'): safe_args[safe_args.index( payload['overwrite_from_index_token'])] = '*****' error_callback = failed_request_callback.s(request.id) try: handle_add_request.apply_async(args=args, link_error=error_callback, argsrepr=repr(safe_args), queue=celery_queue) except kombu.exceptions.OperationalError: handle_broker_error(request) flask.current_app.logger.debug('Successfully scheduled request %d', request.id) return flask.jsonify(request.to_json()), 201
def handle_broker_error(request): """ Handle broker errors by setting the request as failed and raise an IIBError exception. :param Request request: Request which will be set as failed :raises IIBError: Raises IIBError exception after setting request to failed state """ request.add_state('failed', 'The scheduling of the request failed') db.session.commit() messaging.send_message_for_state_change(request) error_message = f'The scheduling of the build request with ID {request.id} failed' current_app.logger.exception(error_message) raise IIBError(error_message)
def handle_broker_batch_error(requests): """ Handle broker errors by setting all requests as failed and raise an IIBError exception. :param list requests: list of all requests that should be marked as failed :raises IIBError: Raises IIBError exception after setting all requests to failed state """ failed_ids = [] for req in requests: failed_ids.append(str(req.id)) req.add_state('failed', 'The scheduling of the request failed') messaging.send_message_for_state_change(req) db.session.commit() error_message = f'The scheduling of the build requests with IDs {", ".join(failed_ids)} failed' current_app.logger.exception(error_message) raise IIBError(error_message)
def merge_index_image(): """ Submit a request to merge two index images. :rtype: flask.Response :raise ValidationError: if required parameters are not supplied """ payload = flask.request.get_json() if not isinstance(payload, dict): raise ValidationError('The input data must be a JSON object') request = RequestMergeIndexImage.from_json(payload) db.session.add(request) db.session.commit() messaging.send_message_for_state_change(request, new_batch_msg=True) overwrite_target_index = payload.get('overwrite_target_index', False) celery_queue = _get_user_queue(serial=overwrite_target_index) args = [ payload['source_from_index'], payload.get('deprecation_list', []), request.id, payload.get('binary_image'), payload.get('target_index'), overwrite_target_index, payload.get('overwrite_target_index_token'), request.distribution_scope, flask.current_app.config['IIB_BINARY_IMAGE_CONFIG'], payload.get('build_tags', []), ] safe_args = _get_safe_args(args, payload) error_callback = failed_request_callback.s(request.id) try: handle_merge_request.apply_async(args=args, link_error=error_callback, argsrepr=repr(safe_args), queue=celery_queue) except kombu.exceptions.OperationalError: handle_broker_error(request) flask.current_app.logger.debug('Successfully scheduled request %d', request.id) return flask.jsonify(request.to_json()), 201
def rm_operators(): """ Submit a request to remove operators from an index image. :rtype: flask.Response :raise ValidationError: if required parameters are not supplied """ payload = flask.request.get_json() if not isinstance(payload, dict): raise ValidationError('The input data must be a JSON object') request = RequestRm.from_json(payload) db.session.add(request) db.session.commit() messaging.send_message_for_state_change(request, new_batch_msg=True) args = [ payload['operators'], payload['binary_image'], request.id, payload['from_index'], payload.get('add_arches'), _should_force_overwrite() or payload.get('overwrite_from_index'), payload.get('overwrite_from_index_token'), ] safe_args = copy.copy(args) if payload.get('overwrite_from_index_token'): safe_args[safe_args.index( payload['overwrite_from_index_token'])] = '*****' error_callback = failed_request_callback.s(request.id) handle_rm_request.apply_async( args=args, link_error=error_callback, argsrepr=repr(safe_args), queue=_get_user_queue(), ) flask.current_app.logger.debug('Successfully scheduled request %d', request.id) return flask.jsonify(request.to_json()), 201
def create_empty_index(): """ Submit a request to create an index image without bundles. Note: Any duplicate bundle will be removed from payload. :rtype: flask.Response :raise ValidationError: if required parameters are not supplied """ payload = flask.request.get_json() if not isinstance(payload, dict): raise ValidationError('The input data must be a JSON object') request = RequestCreateEmptyIndex.from_json(payload) db.session.add(request) db.session.commit() messaging.send_message_for_state_change(request, new_batch_msg=True) args = [ payload['from_index'], request.id, payload.get('output_fbc'), payload.get('binary_image'), payload.get('labels'), flask.current_app.config['IIB_BINARY_IMAGE_CONFIG'], ] safe_args = _get_safe_args(args, payload) error_callback = failed_request_callback.s(request.id) try: handle_create_empty_index_request.apply_async( args=args, link_error=error_callback, argsrepr=repr(safe_args), queue=_get_user_queue()) except kombu.exceptions.OperationalError: handle_broker_error(request) flask.current_app.logger.debug('Successfully scheduled request %d', request.id) return flask.jsonify(request.to_json()), 201
def add_bundles(): """ Submit a request to add operator bundles to an index image. Note: Any duplicate bundle will be removed from payload. :rtype: flask.Response :raise ValidationError: if required parameters are not supplied """ payload = flask.request.get_json() if not isinstance(payload, dict): raise ValidationError('The input data must be a JSON object') # Only run `_get_unique_bundles` if it is a list. If it's not, `from_json` # will raise an error to the user. if payload.get('bundles') and isinstance(payload['bundles'], list): payload['bundles'] = _get_unique_bundles(payload['bundles']) request = RequestAdd.from_json(payload) db.session.add(request) db.session.commit() messaging.send_message_for_state_change(request, new_batch_msg=True) overwrite_from_index = payload.get('overwrite_from_index') celery_queue = _get_user_queue(serial=overwrite_from_index) args = _get_add_args(payload, request, overwrite_from_index, celery_queue) safe_args = _get_safe_args(args, payload) error_callback = failed_request_callback.s(request.id) try: handle_add_request.apply_async(args=args, link_error=error_callback, argsrepr=repr(safe_args), queue=celery_queue) except kombu.exceptions.OperationalError: handle_broker_error(request) flask.current_app.logger.debug('Successfully scheduled request %d', request.id) return flask.jsonify(request.to_json()), 201
def patch_request(request_id): """ Modify the given request. :param int request_id: the request ID from the URL :return: a Flask JSON response :rtype: flask.Response :raise Forbidden: If the user trying to patch a request is not an IIB worker :raise NotFound: if the request is not found :raise ValidationError: if the JSON is invalid """ allowed_users = flask.current_app.config['IIB_WORKER_USERNAMES'] # current_user.is_authenticated is only ever False when auth is disabled if current_user.is_authenticated and current_user.username not in allowed_users: raise Forbidden('This API endpoint is restricted to IIB workers') payload = flask.request.get_json() if not isinstance(payload, dict): raise ValidationError('The input data must be a JSON object') if not payload: raise ValidationError( 'At least one key must be specified to update the request') request = Request.query.get_or_404(request_id) invalid_keys = payload.keys() - request.get_mutable_keys() if invalid_keys: raise ValidationError('The following keys are not allowed: {}'.format( ', '.join(invalid_keys))) for key, value in payload.items(): if key == 'arches': Architecture.validate_architecture_json(value) elif key == 'bundle_mapping': exc_msg = f'The "{key}" key must be an object with the values as lists of strings' if not isinstance(value, dict): raise ValidationError(exc_msg) for v in value.values(): if not isinstance(v, list) or any(not isinstance(s, str) for s in v): raise ValidationError(exc_msg) elif not value or not isinstance(value, str): raise ValidationError( f'The value for "{key}" must be a non-empty string') if 'state' in payload and 'state_reason' not in payload: raise ValidationError( 'The "state_reason" key is required when "state" is supplied') elif 'state_reason' in payload and 'state' not in payload: raise ValidationError( 'The "state" key is required when "state_reason" is supplied') state_updated = False if 'state' in payload and 'state_reason' in payload: RequestStateMapping.validate_state(payload['state']) new_state = payload['state'] new_state_reason = payload['state_reason'] # This is to protect against a Celery task getting executed twice and setting the # state each time if request.state.state == new_state and request.state.state_reason == new_state_reason: flask.current_app.logger.info( 'Not adding a new state since it matches the last state') else: request.add_state(new_state, new_state_reason) state_updated = True image_keys = ( 'binary_image_resolved', 'bundle_image', 'from_bundle_image_resolved', 'from_index_resolved', 'index_image', ) for key in image_keys: if key not in payload: continue key_value = payload.get(key, None) key_object = Image.get_or_create(key_value) # SQLAlchemy will not add the object to the database if it's already present setattr(request, key, key_object) for arch in payload.get('arches', []): request.add_architecture(arch) for operator, bundles in payload.get('bundle_mapping', {}).items(): operator_img = Operator.get_or_create(operator) for bundle in bundles: bundle_img = Image.get_or_create(bundle) bundle_img.operator = operator_img db.session.commit() if state_updated: messaging.send_message_for_state_change(request) if current_user.is_authenticated: flask.current_app.logger.info('The user %s patched request %d', current_user.username, request.id) else: flask.current_app.logger.info('An anonymous user patched request %d', request.id) return flask.jsonify(request.to_json()), 200