def update_type(public_id: int, data: dict): """ HTTP `PUT`/`PATCH` route for update a single type resource. Args: public_id (int): Public ID of the updatable type data (TypeModel.SCHEMA): New type data to update Raises: ManagerGetError: When the type with the `public_id` was not found. ManagerUpdateError: When something went wrong during the update. Returns: UpdateSingleResponse: With update result of the new updated type. """ type_manager = TypeManager(database_manager=current_app.database_manager) try: type_ = TypeModel.from_data(data=data) type_manager.update(public_id=PublicID(public_id), type=TypeModel.to_json(type_)) api_response = UpdateSingleResponse(result=data, url=request.url, model=TypeModel.MODEL) except ManagerGetError as err: return abort(404, err.message) except ManagerUpdateError as err: return abort(400, err.message) return api_response.make_response()
def insert_type(data: dict): """ HTTP `POST` route for insert a single type resource. Args: data (TypeModel.SCHEMA): Insert data of a new type. Raises: ManagerGetError: If the inserted resource could not be found after inserting. ManagerInsertError: If something went wrong during insertion. Returns: InsertSingleResponse: Insert response with the new type and its public_id. """ type_manager = TypeManager(database_manager=current_app.database_manager) data.setdefault('creation_time', datetime.utcnow()) try: result_id: PublicID = type_manager.insert(data) raw_doc = type_manager.get(public_id=result_id) except ManagerGetError as err: return abort(404, err.message) except ManagerInsertError as err: return abort(400, err.message) api_response = InsertSingleResponse(result_id, raw=TypeModel.to_json(raw_doc), url=request.url, model=TypeModel.MODEL) return api_response.make_response(prefix='types')
def get_unstructured_objects(public_id: int, request_user: UserModel): """ HTTP `GET`/`HEAD` route for a multi resources which are not formatted according the type structure. Args: public_id (int): Public ID of the type. Raises: ManagerGetError: When the selected type does not exists or the objects could not be loaded. Returns: GetListResponse: Which includes the json data of multiple objects. """ type_manager = TypeManager(database_manager=current_app.database_manager) object_manager = CmdbObjectManager( database_manager=current_app.database_manager) try: type: TypeModel = type_manager.get(public_id=public_id) objects: List[CmdbObject] = object_manager.get_objects_by( type_id=public_id) except ManagerGetError as err: return abort(400, err.message) type_fields = sorted([field.get('name') for field in type.fields]) unstructured_objects: List[CmdbObject] = [] for object_ in objects: object_fields = [field.get('name') for field in object_.fields] if sorted(object_fields) != type_fields: unstructured_objects.append(object_) api_response = GetListResponse( [un_object.__dict__ for un_object in unstructured_objects], url=request.url, model='Object', body=request.method == 'HEAD') return api_response.make_response()
def add_type(): from bson import json_util type_manager = TypeManager(database_manager=current_app.database_manager) error_collection = {} upload = request.form.get('uploadFile') new_type_list = json.loads(upload, object_hook=json_util.object_hook) for new_type_data in new_type_list: try: new_type_data['public_id'] = object_manager.get_new_id(TypeModel.COLLECTION) new_type_data['creation_time'] = datetime.now(timezone.utc) except TypeError as e: LOGGER.warning(e) return abort(400) try: type_instance = TypeModel.from_data(new_type_data) except CMDBError: return abort(400) try: type_manager.insert(type_instance) except Exception as ex: error_collection.update({"public_id": type_instance.public_id, "message": ex.message}) resp = make_response(error_collection) return resp
def get_type(public_id: int): """ HTTP `GET`/`HEAD` route for a single type resource. Args: public_id (int): Public ID of the type. Raises: ManagerGetError: When the selected type does not exists. Returns: GetSingleResponse: Which includes the json data of a TypeModel. """ type_manager = TypeManager(database_manager=current_app.database_manager) body = request.method == 'HEAD' try: type_ = type_manager.get(public_id) except ManagerGetError as err: return abort(404, err.message) api_response = GetSingleResponse(TypeModel.to_json(type_), url=request.url, model=TypeModel.MODEL, body=body) return api_response.make_response()
def __init__(self, object_instance: CmdbObject, type_instance: TypeModel, render_user: UserModel, object_manager: CmdbObjectManager = None, ref_render=False): self.object_instance: CmdbObject = object_instance self.type_instance: TypeModel = type_instance self.render_user: UserModel = render_user self.object_manager = object_manager if self.object_manager: # TODO: Refactor to pass database-manager in init self.type_manager = TypeManager(self.object_manager.dbm) self.user_manager = UserManager(self.object_manager.dbm) self.ref_render = ref_render
def get_object(public_id, request_user: UserModel): try: object_instance = object_manager.get_object( public_id, user=request_user, permission=AccessControlPermission.READ) except (ObjectManagerGetError, ManagerGetError) as err: return abort(404, err.message) except AccessDeniedError as err: return abort(403, err.message) try: type_instance = TypeManager( database_manager=current_app.database_manager).get( object_instance.get_type_id()) except ObjectManagerGetError as err: LOGGER.error(err.message) return abort(404, err.message) try: render = CmdbRender(object_instance=object_instance, type_instance=type_instance, render_user=request_user, object_manager=object_manager, ref_render=True) render_result = render.result() except RenderError as err: LOGGER.error(err) return abort(500) resp = make_response(render_result) return resp
def verify_import_access(user: UserModel, _type: TypeModel, _manager: TypeManager): """Validate if a user has access to objects of this type.""" location = 'acl.groups.includes.' + str(user.group_id) query = {'$and': [{'$or': [ {'$or': [ {'acl': {'$exists': False}}, {'acl.activated': False}] }, {'$and': [ {'acl.activated': True}, {'$and': [ {location: {'$exists': True}}, {location: {'$all': [ AccessControlPermission.READ.value, AccessControlPermission.CREATE.value, AccessControlPermission.UPDATE.value] } } ] } ] }] }, {'public_id': _type.public_id}]} types_ = _manager.iterate(filter=query, limit=1, skip=0, sort='public_id', order=1) if len([TypeModel.to_json(_) for _ in types_.results]) == 0: raise AccessDeniedError(f'The objects of the type `{_type.name}` are protected by ACL permission!')
def get(self, public_id: Union[PublicID, int], user: UserModel = None, permission: AccessControlPermission = None) -> CmdbObject: """ Get a single object by its id. Args: public_id (int): ID of the object. user: Request user permission: ACL permission Returns: CmdbObject: Instance of CmdbObject with data. """ cursor_result = self._get(self.collection, filter={'public_id': public_id}, limit=1) for resource_result in cursor_result.limit(-1): resource = CmdbObject.from_data(resource_result) type_ = TypeManager(database_manager=self._database_manager).get( resource.type_id) verify_access(type_, user, permission) return resource else: raise ManagerGetError(f'Object with ID: {public_id} not found!')
def get_types(params: CollectionParameters): """ HTTP `GET`/`HEAD` route for getting a iterable collection of resources. Args: params (CollectionParameters): Passed parameters over the http query string Returns: GetMultiResponse: Which includes a IterationResult of the TypeModel. Example: You can pass any parameter based on the CollectionParameters. Optional parameters are passed over the function declaration. Raises: FrameworkIterationError: If the collection could not be iterated. ManagerGetError: If the collection could not be found. """ type_manager = TypeManager(database_manager=current_app.database_manager) body = request.method == 'HEAD' try: iteration_result: IterationResult[TypeModel] = type_manager.iterate( filter=params.filter, limit=params.limit, skip=params.skip, sort=params.sort, order=params.order) types = [TypeModel.to_json(type) for type in iteration_result.results] api_response = GetMultiResponse(types, total=iteration_result.total, params=params, url=request.url, model=TypeModel.MODEL, body=body) except FrameworkIterationError as err: return abort(400, err.message) except ManagerGetError as err: return abort(404, err.message) return api_response.make_response()
def __init__(self): scr = SystemConfigReader() self.database_manager = DatabaseManagerMongo( **scr.get_all_values_from_section('Database')) self.object_manager = CmdbObjectManager( database_manager=self.database_manager) self.category_manager = CategoryManager( database_manager=self.database_manager) self.type_manager = TypeManager(database_manager=self.database_manager) self.log_manager = ExportdLogManager( database_manager=self.database_manager) super().__init__(self.database_manager)
def __init__(self, object_list: List[CmdbObject], request_user: UserModel, database_manager: DatabaseManagerMongo, ref_render=False, object_manager: CmdbObjectManager = None): self.object_list: List[CmdbObject] = object_list self.request_user = request_user self.ref_render = ref_render self.object_manager = object_manager self.type_manager = TypeManager(database_manager=database_manager) self.user_manager = UserManager(database_manager=database_manager)
def delete_type(public_id: int): """ HTTP `DELETE` route for delete a single type resource. Args: public_id (int): Public ID of the deletable type Raises: ManagerGetError: When the type with the `public_id` was not found. ManagerDeleteError: When something went wrong during the deletion. Notes: Deleting the type will also delete all objects in this type! Returns: DeleteSingleResponse: Delete result with the deleted type as data. """ type_manager = TypeManager(database_manager=current_app.database_manager) from cmdb.framework.cmdb_object_manager import CmdbObjectManager deprecated_object_manager = CmdbObjectManager( database_manager=current_app.database_manager) try: objects_ids = [ object_.get_public_id() for object_ in deprecated_object_manager.get_objects_by_type(public_id) ] deprecated_object_manager.delete_many_objects({'type_id': public_id}, objects_ids, None) deleted_type = type_manager.delete(public_id=PublicID(public_id)) api_response = DeleteSingleResponse( raw=TypeModel.to_json(deleted_type), model=TypeModel.MODEL) except ManagerGetError as err: return abort(404, err.message) except ManagerDeleteError as err: return abort(400, err.message) except Exception as err: return abort(400, str(err)) return api_response.make_response()
def import_objects(request_user: UserModel): # Check if file exists if not request.files: return abort(400, 'No import file was provided') request_file: FileStorage = get_file_in_request('file', request.files) filename = secure_filename(request_file.filename) working_file = f'/tmp/{filename}' request_file.save(working_file) # Load file format file_format = request.form.get('file_format', None) # Load parser config parser_config: dict = get_element_from_data_request('parser_config', request) or {} if parser_config == {}: LOGGER.info('No parser config was provided - using default parser config') # Check for importer config importer_config_request: dict = get_element_from_data_request('importer_config', request) or None if not importer_config_request: return abort(400, 'No import config was provided') # Check if type exists try: type_manager = TypeManager(database_manager=current_app.database_manager) type_ = type_manager.get(importer_config_request.get('type_id')) if not type_.active: raise AccessDeniedError(f'Objects cannot be created because type `{type_.name}` is deactivated.') verify_import_access(user=request_user, _type=type_, _manager=type_manager) except ObjectManagerGetError as err: return abort(404, err.message) except AccessDeniedError as err: return abort(403, err.message) # Load parser try: parser_class = load_parser_class('object', file_format) except ParserLoadError as ple: return abort(406, ple.message) parser = parser_class(parser_config) LOGGER.info(f'Parser {parser_class} was loaded') # Load importer config try: importer_config_class = load_importer_config_class('object', file_format) except ImporterLoadError as ile: return abort(406, ile.message) importer_config = importer_config_class(**importer_config_request) LOGGER.debug(importer_config_request) # Load importer try: importer_class = load_importer_class('object', file_format) except ImporterLoadError as ile: return abort(406, ile.message) importer = importer_class(working_file, importer_config, parser, object_manager, request_user) LOGGER.info(f'Importer {importer_class} was loaded') try: import_response: ImporterObjectResponse = importer.start_import() except ImportRuntimeError as ire: LOGGER.error(f'Error while importing objects: {ire.message}') return abort(500, ire.message) except AccessDeniedError as err: return abort(403, err.message) # close request file request_file.close() # log all successful imports for message in import_response.success_imports: try: # get object state of every imported object current_type_instance = object_manager.get_type(importer_config_request.get('type_id')) current_object = object_manager.get_object(message.public_id) current_object_render_result = CmdbRender(object_instance=current_object, type_instance=current_type_instance, render_user=request_user, object_manager=object_manager).result() # insert object create log log_params = { 'object_id': message.public_id, 'user_id': request_user.get_public_id(), 'user_name': request_user.get_display_name(), 'comment': 'Object was imported', 'render_state': json.dumps(current_object_render_result, default=default).encode('UTF-8'), 'version': current_object.version } log_manager.insert(action=LogAction.CREATE, log_type=CmdbObjectLog.__name__, **log_params) except ObjectManagerGetError as err: LOGGER.error(err) return abort(404) except RenderError as err: LOGGER.error(err) return abort(500) except LogManagerInsertError as err: LOGGER.error(err) return make_response(import_response)
def update_unstructured_objects(public_id: int, request_user: UserModel): """ HTTP `PUT`/`PATCH` route for a multi resources which will be formatted based on the TypeModel Args: public_id (int): Public ID of the type. Raises: ManagerGetError: When the type with the `public_id` was not found. ManagerUpdateError: When something went wrong during the update. Returns: UpdateMultiResponse: Which includes the json data of multiple updated objects. """ type_manager = TypeManager(database_manager=current_app.database_manager) object_manager = CmdbObjectManager( database_manager=current_app.database_manager) try: update_type_instance = type_manager.get(public_id) type_fields = update_type_instance.fields objects_by_type = object_manager.get_objects_by_type(type_id=public_id) for obj in objects_by_type: incorrect = [] correct = [] obj_fields = obj.get_all_fields() for t_field in type_fields: name = t_field["name"] for field in obj_fields: if name == field["name"]: correct.append(field["name"]) else: incorrect.append(field["name"]) removed_type_fields = [ item for item in incorrect if not item in correct ] for field in removed_type_fields: object_manager.remove_object_fields( filter_query={'public_id': obj.public_id}, update={'$pull': { 'fields': { "name": field } }}) objects_by_type = object_manager.get_objects_by_type(type_id=public_id) for obj in objects_by_type: for t_field in type_fields: name = t_field["name"] value = None if [ item for item in obj.get_all_fields() if item["name"] == name ]: continue if "value" in t_field: value = t_field["value"] object_manager.update_object_fields( filter={'public_id': obj.public_id}, update={ '$addToSet': { 'fields': { "name": name, "value": value } } }) except ManagerUpdateError as err: return abort(400, err.message) api_response = UpdateMultiResponse([], url=request.url, model='Object') return api_response.make_response()
def __init__(self, database_manager=None, event_queue: Queue = None): self._event_queue = event_queue self._type_manager = TypeManager(database_manager) super(CmdbObjectManager, self).__init__(database_manager)
class CmdbObjectManager(CmdbManagerBase): def __init__(self, database_manager=None, event_queue: Queue = None): self._event_queue = event_queue self._type_manager = TypeManager(database_manager) super(CmdbObjectManager, self).__init__(database_manager) def is_ready(self) -> bool: return self.dbm.status() def get_new_id(self, collection: str) -> int: return self.dbm.get_next_public_id(collection) def aggregate(self, collection, pipeline: Pipeline, **kwargs): try: return self._aggregate(collection=collection, pipeline=pipeline, **kwargs) except Exception as err: raise ObjectManagerGetError(err) def get_object(self, public_id: int, user: UserModel = None, permission: AccessControlPermission = None) -> CmdbObject: try: resource = CmdbObject(**self._get( collection=CmdbObject.COLLECTION, public_id=public_id)) except Exception as err: raise ObjectManagerGetError(str(err)) type_ = self._type_manager.get(resource.type_id) verify_access(type_, user, permission) return resource def get_objects_by(self, sort='public_id', direction=-1, user: UserModel = None, permission: AccessControlPermission = None, **requirements): ack = [] objects = self._get_many(collection=CmdbObject.COLLECTION, sort=sort, direction=direction, **requirements) for obj in objects: object_ = CmdbObject(**obj) try: type_ = self._type_manager.get(object_.type_id) verify_access(type_, user, permission) except CMDBError: continue ack.append(CmdbObject(**obj)) return ack def get_objects_by_type(self, type_id: int): return self.get_objects_by(type_id=type_id) def count_objects_by_type(self, public_id: int): """This method does not actually performs the find() operation but instead returns a numerical count of the documents that meet the selection criteria. Args: public_id (int): public id of document Returns: returns the count of the documents """ formatted_type_id = {'type_id': public_id} return self.dbm.count(CmdbObject.COLLECTION, formatted_type_id) def group_objects_by_value(self, value: str, match=None, user: UserModel = None, permission: AccessControlPermission = None): """This method does not actually performs the find() operation but instead returns a objects grouped by type of the documents that meet the selection criteria. Args: value (str): grouped by value match (dict): stage filters the documents to only pass documents. user (UserModel): request user permission (AccessControlPermission): ACL operations Returns: returns the objects grouped by value of the documents """ ack = [] agr = [] if match: agr.append({'$match': match}) agr.append({ '$group': { '_id': '$' + value, 'result': {'$first': '$$ROOT'}, 'count': {'$sum': 1}, } }) agr.append({'$sort': {'count': -1}}) objects = self.dbm.aggregate(CmdbObject.COLLECTION, agr) for obj in objects: object_ = CmdbObject(**obj['result']) try: type_ = self._type_manager.get(object_.type_id) verify_access(type_, user, permission) except CMDBError: continue ack.append(obj) return ack def sort_objects_by_field_value(self, value: str, order=-1, match=None): """This method does not actually performs the find() operation but instead returns a objects sorted by value of the documents that meet the selection criteria. Args: value (str): sorted by value order : Ascending/Descending Sort e.g. -1 match (dict): stage filters the documents to only pass documents. Returns: returns the list of CMDB Objects sorted by value of the documents """ agr = [] if match: agr.append({'$match': match}) agr.append({"$addFields": { "order": { "$filter": { "input": "$fields", "as": "fields", "cond": {"$eq": ["$$fields.name", value]} } } }}) agr.append({'$sort': {'order': order}}) object_list = [] cursor = self.dbm.aggregate(CmdbObject.COLLECTION, agr) for document in cursor: put_data = json.loads(json_util.dumps(document), object_hook=object_hook) object_list.append(CmdbObject(**put_data)) return object_list def count_objects(self): return self.dbm.count(collection=CmdbObject.COLLECTION) def _find_query_fields(self, query, match_fields=None): match_fields = match_fields or list() for key, items in query.items(): if isinstance(items, dict): if 'fields.value' == key: match_fields.append(items['$regex']) else: for item in items: self._find_query_fields(item, match_fields=match_fields) return match_fields def insert_object(self, data: (CmdbObject, dict), user: UserModel = None, permission: AccessControlPermission = None) -> int: """ Insert new CMDB Object Args: data: init data user: current user, to detect who triggered event permission: extended user acl rights Returns: Public ID of the new object in database """ new_object = None if isinstance(data, dict): try: new_object = CmdbObject(**data) except CMDBError as e: LOGGER.debug(f'Error while inserting object - error: {e.message}') raise ObjectManagerInsertError(e) elif isinstance(data, CmdbObject): new_object = data type_ = self._type_manager.get(new_object.type_id) if not type_.active: raise AccessDeniedError(f'Objects cannot be created because type `{type_.name}` is deactivated.') verify_access(type_, user, permission) try: ack = self.dbm.insert( collection=CmdbObject.COLLECTION, data=new_object.__dict__ ) if self._event_queue: event = Event("cmdb.core.object.added", {"id": new_object.get_public_id(), "type_id": new_object.get_type_id(), "user_id": new_object.author_id, "event": 'insert'}) self._event_queue.put(event) except (CMDBError, PublicIDAlreadyExists) as e: raise ObjectInsertError(e) return ack def get_object_references(self, public_id: int, active_flag=None, user: UserModel = None, permission: AccessControlPermission = None) -> list: # Type of given object id type_id = self.get_object(public_id=public_id, user=user, permission=permission).type_id # query for all types with ref input type with value of type id req_type_query = { "$and": [ {'fields': {'$elemMatch': {'type': 'ref'}}}, {'$or': [ {'fields': {'$elemMatch': {'ref_types': type_id}}}, {'fields': {'$elemMatch': {'ref_types': {'$in': [type_id]}}}} ] } ] } # get type list with given query req_type_list = self.get_types_by(**req_type_query) type_init_list = [] for new_type_init in req_type_list: try: current_loop_type = new_type_init ref_fields = current_loop_type.get_fields_of_type_with_value(input_type='ref', _filter='ref_types', value=type_id) for ref_field in ref_fields: type_init_list.append( {"type_id": current_loop_type.get_public_id(), "field_name": ref_field['name']}) except (CMDBError, FieldNotFoundError, FieldInitError): continue referenced_by_objects = [] for possible_object_types in type_init_list: referenced_query = {"type_id": possible_object_types['type_id'], "fields": { "$elemMatch": {"$and": [{"name": possible_object_types['field_name']}], "$or": [{"value": int(public_id)}, {"value": str(public_id)}]}}} if active_flag: referenced_query.update({'active': {"$eq": True}}) referenced_by_objects = referenced_by_objects + self.get_objects_by(**referenced_query, user=user, permission=permission) return referenced_by_objects def delete_object(self, public_id: int, user: UserModel, permission: AccessControlPermission = None): type_id = self.get_object(public_id=public_id).type_id type_ = self._type_manager.get(type_id) if not type_.active: raise AccessDeniedError(f'Objects cannot be removed because type `{type_.name}` is deactivated.') verify_access(type_, user, permission) try: if self._event_queue: event = Event("cmdb.core.object.deleted", {"id": public_id, "type_id": self.get_object(public_id).get_type_id(), "user_id": user.get_public_id(), "event": 'delete'}) self._event_queue.put(event) ack = self._delete(CmdbObject.COLLECTION, public_id) return ack except (CMDBError, Exception): raise ObjectDeleteError(msg=public_id) def delete_many_objects(self, filter_query: dict, public_ids, user: UserModel): ack = self._delete_many(CmdbObject.COLLECTION, filter_query) if self._event_queue: event = Event("cmdb.core.objects.deleted", {"ids": public_ids, "user_id": user.get_public_id(), "event": 'delete'}) self._event_queue.put(event) return ack @deprecated def get_all_types(self) -> List[TypeModel]: try: raw_types: List[dict] = self._get_many(collection=TypeModel.COLLECTION) except Exception as err: raise ObjectManagerGetError(err=err) try: return [TypeModel.from_data(type) for type in raw_types] except Exception as err: raise ObjectManagerInitError(err=err) @deprecated def get_type(self, public_id: int): try: return TypeModel.from_data(self.dbm.find_one( collection=TypeModel.COLLECTION, public_id=public_id) ) except RequiredInitKeyNotFoundError as err: raise ObjectManagerInitError(err=err.message) except Exception as err: raise ObjectManagerGetError(err=err) @deprecated def get_types_by(self, sort='public_id', **requirements): try: return [TypeModel.from_data(data) for data in self._get_many(collection=TypeModel.COLLECTION, sort=sort, **requirements)] except Exception as err: raise ObjectManagerGetError(err=err) @deprecated def get_type_aggregate(self, arguments): """This method does not actually performs the find() operation but instead returns a objects sorted by value of the documents that meet the selection criteria. Args: arguments: query search for Returns: returns the list of CMDB Types sorted by value of the documents """ type_list = [] cursor = self.dbm.aggregate(TypeModel.COLLECTION, arguments) for document in cursor: put_data = json.loads(json_util.dumps(document), object_hook=object_hook) type_list.append(TypeModel.from_data(put_data)) return type_list @deprecated def count_types(self): return self.dbm.count(collection=TypeModel.COLLECTION) @deprecated def get_categories(self) -> List[CategoryModel]: """Get all categories as nested list""" try: raw_categories = self._get_many(collection=CategoryModel.COLLECTION, sort='public_id') except Exception as err: raise ObjectManagerGetError(err) try: return [CategoryModel.from_data(category) for category in raw_categories] except Exception as err: raise ObjectManagerInitError(err) @deprecated def get_category_by(self, **requirements) -> CategoryModel: """Get a single category by requirements Notes: Even if multiple categories match the requirements only the first matched will be returned """ try: raw_category = self._get_by(collection=CategoryModel.COLLECTION, **requirements) except Exception as err: raise ObjectManagerGetError(err) try: return CategoryModel.from_data(raw_category) except Exception as err: raise ObjectManagerInitError(err) @deprecated def get_categories_by(self, sort='public_id', **requirements: dict) -> List[CategoryModel]: """Get a list of categories by special requirements""" try: raw_categories = self._get_many(collection=CategoryModel.COLLECTION, sort=sort, **requirements) except Exception as err: raise ObjectManagerGetError(err) try: return [CategoryModel.from_data(category) for category in raw_categories] except Exception as err: raise ObjectManagerInitError(err) @deprecated def insert_category(self, category: CategoryModel): """Add a new category into the database or add the children list an existing category""" try: return self._insert(collection=CategoryModel.COLLECTION, data=CategoryModel.to_json(category)) except Exception as err: raise ObjectManagerInsertError(err=err)
def __init__(self, database_manager: DatabaseManagerMongo): self.__type_manager = TypeManager(database_manager=database_manager) super(CategoryManager, self).__init__(CategoryModel.COLLECTION, database_manager=database_manager)
class CategoryManager(FrameworkManager): def __init__(self, database_manager: DatabaseManagerMongo): self.__type_manager = TypeManager(database_manager=database_manager) super(CategoryManager, self).__init__(CategoryModel.COLLECTION, database_manager=database_manager) def iterate(self, filter: dict, limit: int, skip: int, sort: str, order: int, *args, **kwargs) \ -> IterationResult[CategoryModel]: """ Iterate over a collection of type resources. Args: filter: match requirements of field values limit: max number of elements to return skip: number of elements to skip first sort: sort field order: sort order Returns: IterationResult: Instance of IterationResult with generic CategoryModel. """ try: query: Query = self.builder.build(filter=filter, limit=limit, skip=skip, sort=sort, order=order) aggregation_result = next(self._aggregate(self.collection, query)) except ManagerGetError as err: raise ManagerIterationError(err=err) iteration_result: IterationResult[ CategoryModel] = IterationResult.from_aggregation( aggregation_result) iteration_result.convert_to(CategoryModel) return iteration_result def get(self, public_id: Union[PublicID, int]) -> CategoryModel: """ Get a single type by its id. Args: public_id (int): ID of the type. Returns: CategoryModel: Instance of CategoryModel with data. """ cursor_result = self._get(self.collection, filter={'public_id': public_id}, limit=1) for resource_result in cursor_result.limit(-1): return CategoryModel.from_data(resource_result) raise ManagerGetError(f'Category with ID: {public_id} not found!') def insert(self, category: dict) -> PublicID: """ Insert a single category into the system. Args: category (dict): Raw data of the category. Notes: If no public id was given, the database manager will auto insert the next available ID. Returns: int: The Public ID of the new inserted category """ if isinstance(category, CategoryModel): category = CategoryModel.to_json(category) return self._insert(self.collection, resource=category) def update(self, public_id: Union[PublicID, int], category: Union[CategoryModel, dict]): """ Update a existing category in the system. Args: public_id (int): PublicID of the category in the system. category: New category data Notes: If a CategoryModel instance was passed as type argument, \ it will be auto converted via the model `to_json` method. """ if isinstance(category, CategoryModel): category = CategoryModel.to_json(category) update_result = self._update(self.collection, filter={'public_id': public_id}, resource=category) if update_result.matched_count != 1: raise ManagerUpdateError(f'Something happened during the update!') return update_result def delete(self, public_id: Union[PublicID, int]) -> CategoryModel: """ Delete a existing category by its PublicID. Args: public_id (int): PublicID of the type in the system. Returns: CategoryModel: The deleted type as its model. """ raw_category = self.get(public_id=public_id) delete_result = self._delete(self.collection, filter={'public_id': public_id}) if delete_result.deleted_count == 0: raise FrameworkDeleteError( err='No category matched this public id') return raw_category @property def tree(self) -> CategoryTree: """ Get the complete category list as nested tree. Returns: CategoryTree: Categories as tree structure. """ # Find all types types = self.__type_manager.find({}).results categories = self.iterate(filter={}, limit=0, skip=0, sort='public_id', order=1).results return CategoryTree(categories=categories, types=types)
from cmdb.utils import json_encoding from cmdb.utils.helpers import load_class from cmdb.utils.system_config import SystemConfigReader from cmdb.database.managers import DatabaseManagerMongo from cmdb.exporter.exporter_utils import ExperterUtils from cmdb.exporter.format.format_base import BaseExporterFormat from cmdb.exporter.config.config_type import ExporterConfigType from cmdb.framework.managers.type_manager import TypeManager from cmdb.framework.cmdb_object_manager import CmdbObjectManager from cmdb.framework.cmdb_render import RenderResult database_manager = DatabaseManagerMongo( **SystemConfigReader().get_all_values_from_section('Database')) object_manager = CmdbObjectManager(database_manager=database_manager) type_manager = TypeManager(database_manager=database_manager) class ZipExportType(BaseExporterFormat): FILE_EXTENSION = "zip" LABEL = "ZIP" MULTITYPE_SUPPORT = True ICON = "file-archive" DESCRIPTION = "Export Zipped Files" ACTIVE = True def export(self, data: List[RenderResult], *args): """ Export a zip file, containing the object list sorted by type in several files.
from cmdb.interface.api_parameters import CollectionParameters from cmdb.interface.response import GetMultiResponse, GetListResponse, UpdateMultiResponse, UpdateSingleResponse,\ ResponseFailedMessage from cmdb.interface.route_utils import make_response, insert_request_user from cmdb.interface.blueprint import APIBlueprint from cmdb.manager import ManagerIterationError, ManagerGetError, ManagerUpdateError from cmdb.security.acl.errors import AccessDeniedError from cmdb.security.acl.permission import AccessControlPermission from cmdb.user_management import UserModel, UserManager from cmdb.utils.error import CMDBError from cmdb.framework.managers.object_manager import ObjectManager with current_app.app_context(): object_manager = CmdbObjectManager(current_app.database_manager, current_app.event_queue) type_manager = TypeManager(database_manager=current_app.database_manager) log_manager = CmdbLogManager(current_app.database_manager) user_manager = UserManager(current_app.database_manager) LOGGER = logging.getLogger(__name__) objects_blueprint = APIBlueprint('objects', __name__) @objects_blueprint.route('/', methods=['GET', 'HEAD']) @objects_blueprint.protect(auth=True, right='base.framework.object.view') @objects_blueprint.parse_collection_parameters(view='native') @insert_request_user def get_objects(params: CollectionParameters, request_user: UserModel): manager = ObjectManager(database_manager=current_app.database_manager)
class CmdbRender: AUTHOR_ANONYMOUS_NAME = 'unknown' def __init__(self, object_instance: CmdbObject, type_instance: TypeModel, render_user: UserModel, object_manager: CmdbObjectManager = None, ref_render=False): self.object_instance: CmdbObject = object_instance self.type_instance: TypeModel = type_instance self.render_user: UserModel = render_user self.object_manager = object_manager if self.object_manager: # TODO: Refactor to pass database-manager in init self.type_manager = TypeManager(self.object_manager.dbm) self.user_manager = UserManager(self.object_manager.dbm) self.ref_render = ref_render @property def object_instance(self) -> CmdbObject: """ Object of the class CmdbObject that has already been instantiated. The data should come from the database and already be validated. This already happens when the object is instantiated. """ return self._object_instance @object_instance.setter def object_instance(self, object_instance: CmdbObject): """ Property setter for object_instance. The render only checks whether the passed object belongs to the correct class, not whether it is valid. """ if not isinstance(object_instance, CmdbObject): raise ObjectInstanceError() else: self._object_instance = object_instance @property def type_instance(self) -> TypeModel: """ Object of the class TypeModel that has already been instantiated. The data should come from the database and already be validated. This already happens when the object is instantiated. """ return self._type_instance @type_instance.setter def type_instance(self, type_instance: TypeModel): """ Property setter for type_instance. The render only checks whether the passed object belongs to the correct class, not whether it is valid. """ if not isinstance(type_instance, TypeModel): raise TypeInstanceError() self._type_instance = type_instance def result(self) -> RenderResult: return self._generate_result() def _generate_result(self) -> RenderResult: render_result = RenderResult() try: render_result = self.__generate_object_information(render_result) render_result = self.__generate_type_information(render_result) render_result = self.__set_fields(render_result) render_result = self.__set_sections(render_result) render_result = self.__set_summaries(render_result) render_result = self.__set_external(render_result) except CMDBError as err: import traceback traceback.print_exc() raise RenderError( f'Error while generating a CMDBResult: {str(err)}') return render_result def __generate_object_information( self, render_result: RenderResult) -> RenderResult: try: author_name = self.user_manager.get( self.object_instance.author_id).get_display_name() except CMDBError: author_name = CmdbRender.AUTHOR_ANONYMOUS_NAME if self.object_instance.editor_id: try: editor_name = self.user_manager.get( self.object_instance.editor_id).get_display_name() except CMDBError: editor_name = None else: editor_name = None render_result.object_information = { 'object_id': self.object_instance.public_id, 'creation_time': self.object_instance.creation_time, 'last_edit_time': self.object_instance.last_edit_time, 'author_id': self.object_instance.author_id, 'author_name': author_name, 'editor_id': self.object_instance.editor_id, 'editor_name': editor_name, 'active': self.object_instance.active, 'version': self.object_instance.version } return render_result def __generate_type_information( self, render_result: RenderResult) -> RenderResult: try: author_name = self.user_manager.get( self.type_instance.author_id).get_display_name() except CMDBError as err: author_name = CmdbRender.AUTHOR_ANONYMOUS_NAME try: self.type_instance.render_meta.icon except KeyError: self.type_instance.render_meta.icon = '' render_result.type_information = { 'type_id': self.type_instance.public_id, 'type_name': self.type_instance.name, 'type_label': self.type_instance.label, 'creation_time': self.type_instance.creation_time, 'author_id': self.type_instance.author_id, 'author_name': author_name, 'icon': self.type_instance.render_meta.icon, 'active': self.type_instance.active, 'version': self.type_instance.version, 'acl': self.type_instance.acl.to_json(self.type_instance.acl) } return render_result def __set_fields(self, render_result: RenderResult) -> RenderResult: render_result.fields = self.__merge_fields_value() return render_result def __set_sections(self, render_result: RenderResult) -> RenderResult: try: render_result.sections = [ section.to_json(section) for section in self.type_instance.render_meta.sections ] except (IndexError, ValueError): render_result.sections = [] return render_result def __merge_field_content_section(self, field: dict, object_: CmdbObject): curr_field = [x for x in object_.fields if x['name'] == field['name']][0] if curr_field['name'] == field['name'] and field.get('value'): field['default'] = field['value'] field['value'] = curr_field['value'] # handle dates that are stored as strings if field['type'] == 'date' and isinstance(field['value'], str) and field['value']: field['value'] = parse(field['value'], fuzzy=True) if self.ref_render and field['type'] == 'ref' and field['value']: field['reference'] = self.__merge_references(field) return field def __merge_fields_value(self) -> List[dict]: """ Checks all fields for references. Fields with references are extended by the property 'references'. All reference values are stored in the new property. """ field_map = [] for idx, section in enumerate(self.type_instance.render_meta.sections): if type(section) is TypeFieldSection and isinstance( section, TypeFieldSection): for section_field in section.fields: field = {} try: field = self.type_instance.get_field(section_field) field = self.__merge_field_content_section( field, self.object_instance) if field['type'] == 'ref' and (not self.ref_render or 'summaries' not in field): ref_field_name: str = field['name'] field = self.type_instance.get_field( ref_field_name) reference_id: int = self.object_instance.get_value( ref_field_name) field['value'] = reference_id reference_object: CmdbObject = self.object_manager.get_object( public_id=reference_id) ref_type: TypeModel = self.type_manager.get( reference_object.get_type_id()) field['reference'] = { 'type_id': ref_type.public_id, 'type_name': ref_type.name, 'type_label': ref_type.label, 'object_id': reference_id, 'summaries': [] } for ref_section_field_name in ref_type.get_fields( ): ref_section_field = ref_type.get_field( ref_section_field_name['name']) try: ref_field = self.__merge_field_content_section( ref_section_field, reference_object) except (FileNotFoundError, ValueError, IndexError): continue field['reference']['summaries'].append( ref_field) except (ValueError, IndexError, FileNotFoundError, ObjectManagerGetError): field['value'] = None field_map.append(field) elif type(section) is TypeReferenceSection and isinstance( section, TypeReferenceSection): ref_field_name: str = f'{section.name}-field' ref_field = self.type_instance.get_field(ref_field_name) reference_id: int = self.object_instance.get_value( ref_field_name) ref_field['value'] = reference_id try: reference_object: CmdbObject = self.object_manager.get_object( public_id=reference_id) except ObjectManagerGetError: reference_object = None ref_type: TypeModel = self.type_manager.get( section.reference.type_id) ref_section = ref_type.get_section( section.reference.section_name) ref_field['references'] = { 'type_id': ref_type.public_id, 'type_name': ref_type.name, 'type_label': ref_type.label, 'fields': [] } if not ref_section: continue if not section.reference.selected_fields or len( section.reference.selected_fields) == 0: selected_ref_fields = ref_section.fields section.reference.selected_fields = selected_ref_fields self.type_instance.render_meta.sections[idx] = section else: selected_ref_fields = [ f for f in ref_section.fields if f in section.reference.selected_fields ] for ref_section_field_name in selected_ref_fields: ref_section_field = ref_type.get_field( ref_section_field_name) if reference_object: try: ref_section_field = self.__merge_field_content_section( ref_section_field, reference_object) except (FileNotFoundError, ValueError, IndexError, ObjectManagerGetError): continue ref_field['references']['fields'].append(ref_section_field) field_map.append(ref_field) return field_map def __merge_references(self, current_field): # Initialise TypeReference reference = TypeReference(type_id=0, object_id=0, type_label='', line='') if current_field['value']: try: ref_object = self.object_manager.get_object( int(current_field['value']), user=self.render_user, permission=AccessControlPermission.READ) except AccessDeniedError as err: return err.message except ObjectManagerGetError: return TypeReference.to_json(reference) try: ref_type = self.object_manager.get_type( ref_object.get_type_id()) _summary_fields = [] _nested_summaries = self.type_instance.get_nested_summaries() _nested_summary_fields = ref_type.get_nested_summary_fields( _nested_summaries) _nested_summary_line = ref_type.get_nested_summary_line( _nested_summaries) reference.type_id = ref_type.get_public_id() reference.object_id = int(current_field['value']) reference.type_label = ref_type.label reference.icon = ref_type.get_icon() reference.prefix = ref_type.has_nested_prefix( _nested_summaries) _summary_fields = _nested_summary_fields \ if (_nested_summary_line or _nested_summary_fields) else ref_type.get_summary().fields summaries = [] summary_values = [] for field in _summary_fields: summary_value = str([ x for x in ref_object.fields if x['name'] == field['name'] ][0]['value']) summaries.append({ "value": summary_value, "type": field.get('type') }) summary_values.append(summary_value) reference.summaries = summaries try: # fill the summary line with summaries value data reference.line = _nested_summary_line if not reference.line_requires_fields(): reference.summaries = [] if _nested_summary_line: reference.fill_line(summary_values) except (TypeReferenceLineFillError, Exception): pass except ObjectManagerGetError: return TypeReference.to_json(reference) return TypeReference.to_json(reference) def __set_summaries(self, render_result: RenderResult) -> RenderResult: # global summary list summary_list = [] summary_line = '' default_line = f'{self.type_instance.label} #{self.object_instance.public_id}' if not self.type_instance.has_summaries(): render_result.summaries = summary_list render_result.summary_line = default_line return render_result try: summary_list = self.type_instance.get_summary().fields render_result.summaries = summary_list first = True for line in summary_list: if first: summary_line += f'{line["value"]}' first = False else: summary_line += f' | {line["value"]}' render_result.summary_line = summary_line except Exception: summary_line = default_line finally: render_result.summary_line = summary_line return render_result def __set_external(self, render_result: RenderResult) -> RenderResult: """ get filled external links Returns: list of filled external links (TypeExternalLink) """ # global external list external_list = [] # checks if type has externals defined if not self.type_instance.has_externals(): render_result.externals = [] # loop over all externals for ext_link in self.type_instance.get_externals(): # append all values for required field in this list field_list = [] # if data are missing or empty append here missing_list = [] try: # get TypeExternalLink definitions from type ext_link_instance = self.type_instance.get_external( ext_link.name) # check if link requires data - regex check for {} if ext_link_instance.link_requires_fields(): # check if has fields if not ext_link_instance.has_fields(): raise ValueError(field_list) # for every field get the value data from object_instance for ext_link_field in ext_link_instance.fields: try: if ext_link_field == 'object_id': field_value = self.object_instance.public_id else: field_value = self.object_instance.get_value( ext_link_field) if field_value is None or field_value == '': # if value is empty or does not exists raise ValueError(ext_link_field) field_list.append(field_value) except CMDBError: # if error append missing data missing_list.append(ext_link_instance) if len(missing_list) > 0: raise RuntimeError(missing_list) try: # fill the href with field value data ext_link_instance.fill_href(field_list) except ValueError: continue except (CMDBError, Exception): continue external_list.append(TypeExternalLink.to_json(ext_link_instance)) render_result.externals = external_list return render_result