Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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()
Esempio n. 4
0
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
Esempio n. 5
0
File: errors.py Progetto: zxiong/iib
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)
Esempio n. 6
0
File: errors.py Progetto: zxiong/iib
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)
Esempio n. 7
0
File: api_v1.py Progetto: zanssa/iib
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
Esempio n. 8
0
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
Esempio n. 9
0
File: api_v1.py Progetto: zanssa/iib
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
Esempio n. 10
0
File: api_v1.py Progetto: zanssa/iib
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
Esempio n. 11
0
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