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