Exemple #1
0
def test_package_manager_get_by_name(name, expected, app, db, auth_env):
    pkg_manager: Optional[PackageManager] = PackageManager.get_by_name(name)
    if expected is None:
        assert expected == pkg_manager
    else:
        assert expected == pkg_manager.name
Exemple #2
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 NotFound: if the request is not found
    :raise ValidationError: if the JSON is invalid
    """
    allowed_users = flask.current_app.config['CACHITO_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 Unauthorized(
            'This API endpoint is restricted to Cachito 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')

    valid_keys = {
        'dependencies', 'environment_variables', 'packages', 'pkg_managers',
        'state', 'state_reason'
    }
    invalid_keys = set(payload.keys()) - valid_keys
    if invalid_keys:
        raise ValidationError('The following keys are not allowed: {}'.format(
            ', '.join(invalid_keys)))

    for key, value in payload.items():
        if key in ('dependencies', 'packages',
                   'pkg_managers') and not isinstance(value, list):
            raise ValidationError(f'The value for "{key}" must be an array')

        if key == 'dependencies':
            for dep in value:
                Dependency.validate_json(dep, for_update=True)
        elif key == 'packages':
            for dep in value:
                Package.validate_json(dep)
        elif key == 'pkg_managers':
            for pkg_manager in value:
                if not isinstance(pkg_manager, str):
                    raise ValidationError(
                        'The value for "pkg_managers" must be an array of strings'
                    )
        elif key == 'environment_variables':
            if not isinstance(value, dict):
                raise ValidationError(
                    'The value for "{}" must be an object'.format(key))
            for env_var_name, env_var_value in value.items():
                EnvironmentVariable.validate_json(env_var_name, env_var_value)
        elif not isinstance(value, str):
            raise ValidationError(
                'The value for "{}" must be a string'.format(key))

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

    request = Request.query.get_or_404(request_id)
    delete_bundle = False
    delete_bundle_temp = False
    if 'state' in payload and 'state_reason' in payload:
        new_state = payload['state']
        delete_bundle = new_state == 'stale'
        delete_bundle_temp = new_state in ('complete', 'failed')
        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_name == 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)

    if 'dependencies' in payload:
        for dep_and_replaces in payload['dependencies']:
            dep = copy.deepcopy(dep_and_replaces)
            replaces = dep.pop('replaces', None)

            dep_object = Dependency.get_or_create(dep)
            replaces_object = None
            if replaces:
                replaces_object = Dependency.get_or_create(replaces)
            request.add_dependency(dep_object, replaces_object)

    for package in payload.get('packages', []):
        package_object = Package.get_or_create(package)
        if package_object not in request.packages:
            request.packages.append(package_object)

    if 'pkg_managers' in payload:
        pkg_managers = PackageManager.get_pkg_managers(payload['pkg_managers'])
        for pkg_manager in pkg_managers:
            if pkg_manager not in request.pkg_managers:
                request.pkg_managers.append(pkg_manager)

    for name, value in payload.get('environment_variables', {}).items():
        env_var_obj = EnvironmentVariable.query.filter_by(name=name,
                                                          value=value).first()
        if not env_var_obj:
            env_var_obj = EnvironmentVariable.from_json(name, value)
            db.session.add(env_var_obj)

        if env_var_obj not in request.environment_variables:
            request.environment_variables.append(env_var_obj)

    db.session.commit()
    if delete_bundle and os.path.exists(request.bundle_archive):
        flask.current_app.logger.info('Deleting the bundle archive %s',
                                      request.bundle_archive)
        try:
            os.remove(request.bundle_archive)
        except:  # noqa E722
            flask.current_app.logger.exception(
                'Failed to delete the bundle archive %s',
                request.bundle_archive)

    if delete_bundle_temp and os.path.exists(request.bundle_temp_files):
        flask.current_app.logger.debug(
            'Deleting the temporary files used to create the bundle at %s',
            request.bundle_temp_files,
        )
        try:
            shutil.rmtree(request.bundle_temp_files)
        except:  # noqa E722
            flask.current_app.logger.exception(
                'Failed to delete the temporary files at %s',
                request.bundle_temp_files)

    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
Exemple #3
0
def get_requests():
    """
    Retrieve paginated details for requests.

    :rtype: flask.Response
    """
    # Check if the user is filtering requests by state
    state = flask.request.args.get("state")
    # Default verbose flag to False
    verbose = str_to_bool(flask.request.args.get("verbose", False))
    max_per_page = flask.current_app.config["CACHITO_MAX_PER_PAGE"]
    # The call to `paginate` will inspect the current HTTP request for the
    # pagination parameters `page` and `per_page`.
    query = Request.query.order_by(Request.id.desc())
    if state:
        if state not in RequestStateMapping.get_state_names():
            states = ", ".join(RequestStateMapping.get_state_names())
            raise ValidationError(
                f"{state} is not a valid request state. Valid states are: {states}"
            )
        state_int = RequestStateMapping.__members__[state].value
        query = query.join(RequestState,
                           Request.request_state_id == RequestState.id)
        query = query.filter(RequestState.state == state_int)
    repo = flask.request.args.get("repo")
    if repo:
        query = query.filter(Request.repo == repo)
    ref = flask.request.args.get("ref")
    if ref:
        if not is_request_ref_valid(ref):
            raise ValidationError(f"{ref} is not a valid ref.")
        query = query.filter(Request.ref == ref)
    pkg_managers = flask.request.args.getlist("pkg_manager")
    if pkg_managers:
        pkg_manager_ids = []
        for name in pkg_managers:
            if not name:
                # Ignore if pkg_manager= presents in the querystring
                continue
            pkg_manager: PackageManager = PackageManager.get_by_name(name)
            if pkg_manager is None:
                raise ValidationError(
                    f"Cachito does not have package manager {name}.")
            pkg_manager_ids.append(pkg_manager.id)
        if pkg_manager_ids:
            query = (query.join(PackageManager, Request.pkg_managers).filter(
                PackageManager.id.in_(pkg_manager_ids)).group_by(
                    Request.id).having(
                        func.count(PackageManager.id) == len(pkg_manager_ids)))
    try:
        per_page = int(flask.request.args.get("per_page", 10))
    except ValueError:
        per_page = 10
    pagination_query = query.paginate(per_page=per_page,
                                      max_per_page=max_per_page)
    requests = pagination_query.items
    query_params = {}
    if state:
        query_params["state"] = state
    if verbose:
        query_params["verbose"] = verbose
    response = {
        "items": [request.to_json(verbose=verbose) for request in requests],
        "meta": pagination_metadata(pagination_query, **query_params),
    }
    return flask.jsonify(response)