def select_project(self, project_id: Optional[str] = None, project_name: Optional[str] = None) -> None: """Select project for the given connection based on project_id or project_name. When both `project_id` and `project_name` are `None`, project selection is cleared. When both `project_id` and `project_name` are provided, `project_name` is ignored. Args: project_id: id of project to select project_name: name of project to select Raises: ValueError: if project with given id or name does not exist """ if project_id is None and project_name is None: self.project_id = None self.project_name = None if config.verbose: logger.info('No project selected.') return None if project_id and project_name: tmp_msg = ( 'Both `project_id` and `project_name` arguments provided. ' 'Selecting project based on `project_id`.') helper.exception_handler(msg=tmp_msg, exception_type=Warning) _projects = projects.get_projects(connection=self).json() if project_id: # Find which project name matches the project ID provided tmp_projects = helper.filter_list_of_dicts(_projects, id=project_id) if not tmp_projects: self.project_id, self.project_name = None, None tmp_msg = ( f"Error connecting to project with id: {project_id}. " "Project with given id does not exist or user has no access." ) raise ValueError(tmp_msg) elif project_name: # Find which project ID matches the project name provided tmp_projects = helper.filter_list_of_dicts(_projects, name=project_name) if not tmp_projects: self.project_id, self.project_name = None, None tmp_msg = ( f"Error connecting to project with name: {project_name}. " "Project with given name does not exist or user has no access." ) raise ValueError(tmp_msg) self.project_id = tmp_projects[0]['id'] self.project_name = tmp_projects[0]['name'] self._session.headers['X-MSTR-ProjectID'] = self.project_id
def select_application(self, application_id: str = None, application_name: str = None) -> None: """Select application for the given connection based on application_id or application_name. When both `application_id` and `application_name` are `None`, application selection is cleared. When both `application_id` and `application_name` are provided, `application_name` is ignored. Args: application_id: id of application to select application_name: name of application to select Raises: ValueError: if application with given id or name does not exist """ if application_id is None and application_name is None: self.application_id = None self.application_name = None self.project_id = self.application_id self.project_name = self.application_name if config.verbose: print("No application selected.") return None if application_id is not None and application_name is not None: tmp_msg = ("Both `application_id` and `application_name` arguments provided. " "Selecting application based on `application_id`.") helper.exception_handler(msg=tmp_msg, exception_type=Warning) _applications = projects.get_projects(connection=self).json() if application_id is not None: # Find which application name matches the application ID provided tmp_applications = helper.filter_list_of_dicts(_applications, id=application_id) if not tmp_applications: self.application_id, self.application_name = None, None tmp_msg = (f"Error connecting to application with id: {application_id}. " "Application with given id does not exist or user has no access.") raise ValueError(tmp_msg) elif application_name is not None: # Find which application ID matches the application name provided tmp_applications = helper.filter_list_of_dicts(_applications, name=application_name) if not tmp_applications: self.application_id, self.application_name = None, None tmp_msg = (f"Error connecting to application with name: {application_name}. " "Application with given name does not exist or user has no access.") raise ValueError(tmp_msg) self.application_id = tmp_applications[0]['id'] self.application_name = tmp_applications[0]['name'] self.project_id = self.application_id self.project_name = self.application_name self.session.headers['X-MSTR-ProjectID'] = self.application_id
def _validate_privileges(connection, privileges: Union[List, int, str]) -> List[dict]: """This function validates if the privilege ID/Name/Object is valid and returns the IDs. If invalid, raise ValueError. """ all_privileges = Privilege.list_privileges(connection=connection, to_dictionary=True) validated = [] privileges = privileges if isinstance(privileges, list) else [privileges] for privilege in privileges: is_str_name = type(privilege) == str and len(privilege) > 3 is_str_id = type(privilege) == str and len(privilege) > 0 and len( privilege) <= 3 is_int_id = isinstance(privilege, int) and privilege < 300 and privilege >= 0 privilege_ok = False if is_str_name: temp_priv = helper.filter_list_of_dicts(all_privileges, name=privilege) privilege_ok = bool(temp_priv) if privilege_ok: privilege_id = temp_priv[0]['id'] privilege_name = temp_priv[0]['name'] elif is_str_id or is_int_id: temp_priv = helper.filter_list_of_dicts(all_privileges, id=str(privilege)) privilege_ok = bool(temp_priv) if privilege_ok: privilege_id = temp_priv[0]['id'] privilege_name = temp_priv[0]['name'] elif isinstance(privilege, Privilege): privilege_ok = True privilege_id = privilege.id privilege_name = privilege.name if privilege_ok: validated.append({'id': privilege_id, 'name': privilege_name}) else: helper.exception_handler( "'{}' is not a valid privilege. Possible values can be found in EnumDSSXMLPrivilegeTypes: \nhttps://lw.microstrategy.com/msdz/msdl/GARelease_Current/docs/ReferenceFiles/reference/com/microstrategy/webapi/EnumDSSXMLPrivilegeTypes.html" .format(privilege), exception_type=ValueError) return validated
def unload_app(node): body = { "operationList": [{ "op": "replace", "path": "/status", "value": "unloaded" }] } response = monitors.update_node_properties(self.connection, node, self.id, body, whitelist=[('ERR001', 500)]) if response.status_code == 202: tmp = helper.filter_list_of_dicts(self.nodes, name=node) tmp[0]['projects'] = [response.json()['project']] super(Entity, self).__setattr__('nodes', tmp) if tmp[0]['projects'][0]['status'] != 'unloaded': self.fetch('nodes') if config.verbose: print("'{}' unloaded on node '{}'.".format( self.name, node)) if response.status_code == 500 and config.verbose: # handle whitelisted print("'{}' already unloaded on node '{}'.".format( self.name, node))
def _list_available_dbms(cls, connection: "Connection", to_dictionary: bool = False, limit: int = None, **filters) -> Union[List["Dbms"], List[dict]]: objects = helper.fetch_objects(connection=connection, api=datasources.get_available_dbms, limit=None, filters=None) cls._DBMS_CACHE.update([ cls.from_dict(source=obj, connection=connection) for obj in objects ]) if limit: objects = objects[:limit] if filters: objects = helper.filter_list_of_dicts(objects, **filters) if to_dictionary: return objects else: return [ cls.from_dict(source=obj, connection=connection) for obj in objects ]
def get_job(connection: "Connection", id: str, node_name: str = None, fields: List[str] = None, error_msg: str = None): """Get job information. Args: connection(object): MicroStrategy connection object returned by `connection.Connection()`. node_name(str, optional): Node name, if not passed list jobs on all nodes fields(list, optional): Comma separated top-level field whitelist. This allows client to selectively retrieve part of the response model. error_msg (string, optional): Custom Error Message for Error Handling Returns: HTTP response object returned by the MicroStrategy REST server """ response = Mock() # create empty mock object to mimic REST API response if not node_name: # fetch jobs on all nodes nodes_response = get_node_info(connection).json() nodes = nodes_response['nodes'] node_names = [node["name"] for node in nodes] if isinstance(node_name, str): node_names = [node_names] with FuturesSessionWithRenewal(connection=connection, max_workers=8) as session: futures = [ get_jobs_async(future_session=session, connection=connection, node_name=node) for node in node_names ] jobs = [] for f in futures: response = f.result() if not response.ok: response_handler(response, error_msg, throw_error=False) else: jobs.extend(response.json()['jobs']) job = filter_list_of_dicts(jobs, id=id) if not job: response.status_code = 400 response.reason = f"Error getting job '{id}'" response.raise_for_status() elif len(job) > 1: response.status_code = 400 response.reason = f"More than one job with id '{id}' was found." response.raise_for_status() else: job = job[0] job = json.dumps(job).encode('utf-8') response._content = job response.status_code = 200 return response
def _validate_privileges( connection: Connection, privileges: Union[Union["Privilege", int, str], List[Union["Privilege", int, str]]] ) -> List[dict]: """This function validates if the privilege ID/Name/Object is valid and returns the IDs. If invalid, raise ValueError. """ all_privileges = Privilege.list_privileges(connection=connection, to_dictionary=True) validated = [] privileges = privileges if isinstance(privileges, list) else [privileges] # TODO: This whole thing can probably be made more efficient and elegant # with some list comprehension instead of appending in a loop. for privilege in privileges: is_str_name = type(privilege) == str and len(privilege) > 3 is_str_id = type(privilege) == str and len(privilege) > 0 and len(privilege) <= 3 is_int_id = isinstance(privilege, int) and privilege < 300 and privilege >= 0 privilege_ok = False if is_str_name: temp_priv = helper.filter_list_of_dicts(all_privileges, name=privilege) privilege_ok = bool(temp_priv) elif is_str_id or is_int_id: temp_priv = helper.filter_list_of_dicts(all_privileges, id=str(privilege)) privilege_ok = bool(temp_priv) if privilege_ok: privilege_id = temp_priv[0]['id'] privilege_name = temp_priv[0]['name'] validated.append({'id': privilege_id, 'name': privilege_name}) elif isinstance(privilege, Privilege): privilege_id = privilege.id privilege_name = privilege.name validated.append({'id': privilege_id, 'name': privilege_name}) else: docs_url = ("https://lw.microstrategy.com/msdz/msdl/GARelease_Current/docs/" + "ReferenceFiles/reference/com/microstrategy/webapi/" + "EnumDSSXMLPrivilegeTypes.html") msg = (f"'{privilege}' is not a valid privilege. Possible values can be found in " "EnumDSSXMLPrivilegeTypes: \n" + docs_url) helper.exception_handler(msg, exception_type=ValueError) return validated
def _get_value(self): option_name = [ option['name'] for option in helper.filter_list_of_dicts(self.options, value=self.value) ] if len(option_name) == 1: return option_name[0] else: return option_name
def __setattr__(self, name, value): """Setattr that allows setting valid name values defined in options.""" if name == 'value' and getattr(self, 'options', None): option_found = helper.filter_list_of_dicts(self.options, name=value) if option_found: super().__setattr__(name, option_found[0]['value']) else: super().__setattr__(name, value) else: super().__setattr__(name, value)
def _check_service_running(service_name: str, service_list: List[Dict], node_name: Optional[str] = None) -> bool: """Return True if service is running on any node available. If `node_name` is provided, the service status will be given for the selected node. """ nodes_info = filter_list_of_dicts(service_list, service=service_name)[0]['nodes'] if node_name: node_info = filter_list_of_dicts(nodes_info, node=node_name)[0] if node_info['status'] == 'PASSING': return True else: return False else: return bool( [True for node in nodes_info if node['status'] == 'PASSING'])
def _get_node_info(node_name: str, service_name: str, service_list: List[Dict]) -> Dict: nodes_info = helper.filter_list_of_dicts( service_list, service=service_name)[0]['nodes'] node_info = helper.filter_list_of_dicts(nodes_info, node=node_name) if not node_info: helper.exception_handler( f"Service {service_name} is not available on {node_name}", exception_type=Warning) return None else: node_info = node_info[0] if node_info.get('serviceControl') is False: helper.exception_handler( f"Service {service_name} cannot be controlled on {node_name}", exception_type=Warning) return None else: return node_info
def __not_available(recipient): if recipient in available_recipients_ids: rec = helper.filter_list_of_dicts(available_recipients, id=recipient) formatted_recipients.append(rec[0]) else: msg = ( f"'{recipient}' is not a valid recipient ID for selected content " "and delivery mode. Available recipients: \n" f"{pformat(available_recipients,indent=2)}") helper.exception_handler(msg, ValueError)
def list_members(self, **filters) -> List[dict]: """List usergroup members. Optionally filter the results by passing filter keyword arguments. Args: **filters: Available filter parameters: 'name', 'id', 'type', 'abbreviation', subtype', 'date_created', 'date_modified', 'version', 'acg', 'owner', source', ext_type', 'username', full_name', enabled' """ return helper.filter_list_of_dicts(self.members, **filters)
def grant_privilege( self, privilege: Union[Union["Privilege", int, str], List[Union["Privilege", int, str]]] ) -> None: """Grant new project-level privileges to the Security Role. Args: privilege: list of privilege objects, ids or names """ # get all project level privileges from mstrio.access_and_security.privilege import Privilege project_level = [ priv['id'] for priv in Privilege.list_privileges( self.connection, to_dictionary=True, is_project_level_privilege='True') ] # validate and filter passed privileges privileges = Privilege._validate_privileges(self.connection, privilege) server_level = list({priv['id'] for priv in privileges} - set(project_level)) privileges = helper.filter_list_of_dicts(privileges, id=project_level) # create lists for print purposes privilege_ids = [priv['id'] for priv in privileges] existing_ids = [obj['id'] for obj in self.privileges] succeeded = list(set(privilege_ids) - set(existing_ids)) failed = list(set(existing_ids).intersection(set(privilege_ids))) if server_level: msg = ( "Privileges {} are server-level and will be omitted. Only project-level " "privileges can be granted by this method.").format( sorted(server_level)) helper.exception_handler(msg, exception_type=Warning) self._update_nested_properties( objects=privileges, path="privileges", op="addElement", ) if succeeded: self.fetch( ) # fetch the object properties and set object attributes if config.verbose: logger.info( f"Granted privilege(s) {succeeded} to '{self.name}'") if failed and config.verbose: logger.warning( f"Security Role '{self.name}' already has privilege(s) {failed}" )
def __setattr__(self, name, value): """Setattr that allows setting valid name values defined in options.""" # if setting the value of setting and options are defined # and value is str (option name) value_is_option_name = isinstance(value, str) and value setting_val_with_options = name == 'value' and getattr( self, 'options', None) if setting_val_with_options and value_is_option_name: option_found = helper.filter_list_of_dicts(self.options, name=value) if option_found: value = option_found[0]['value'] super().__setattr__(name, value)
def _list_loaded_projects(cls, connection: Connection, to_dictionary: bool = False, **filters) -> Union[List["Project"], List[dict]]: response = projects.get_projects(connection, whitelist=[('ERR014', 403)]) list_of_dicts = response.json() if response.ok else [] list_of_dicts = helper.camel_to_snake(list_of_dicts) # Convert keys raw_project = helper.filter_list_of_dicts(list_of_dicts, **filters) if to_dictionary: # return list of project names return raw_project else: # return list of Project objects return [cls.from_dict(source=obj, connection=connection) for obj in raw_project]
def create(cls, connection: Connection, name: str, privileges: Union[Union["Privilege", int, str], List[Union["Privilege", int, str]]], description: str = ""): """Create a new Security Role. Args: connection(object): MicroStrategy connection object returned by 'connection.Connection()'. name(string): Name of the Security Role privileges: List of privileges which will be assigned to this security role. Use privilege IDs or Privilege objects. description(string, optional): Description of the Security Role Returns: Newly created Security Role if the HTTP server has successfully created the Security Role. """ # get all project level privileges from mstrio.access_and_security.privilege import Privilege project_level = [ priv['id'] for priv in Privilege.list_privileges( connection, to_dictionary=True, is_project_level_privilege='True') ] # validate and filter passed privileges privileges = Privilege._validate_privileges(connection, privileges) server_level = list({priv['id'] for priv in privileges} - set(project_level)) privileges = helper.filter_list_of_dicts(privileges, id=project_level) body = { "name": name, "description": description, "privileges": privileges } response = security.create_security_role(connection, body) if response.ok: if server_level: msg = ( "Privileges {} are server-level and will be omitted. Only project-level " "privileges can be granted by this method.").format( sorted(server_level)) helper.exception_handler(msg, exception_type=Warning) return cls(connection=connection, id=response.json()['id'])
def list_schedules(self, **filters): """List all schedules. Args: **filters: Available filter parameters:['name':, 'id', 'description', 'scheduleType', 'scheduleNextDelivery',] """ # TODO add limit, and support for objects, to_datafram, to_dictionary response = schedules.list_schedules(self.connection) if response.ok: response = helper.camel_to_snake(response.json()["schedules"]) return helper.filter_list_of_dicts(response, **filters)
def list_schedules(self, **filters): """List all schedules. Args: **filters: Available filter parameters:['name':, 'id', 'description', 'scheduleType', 'scheduleNextDelivery',] """ response = schedules.list_schedules(self.connection) if response.ok: return helper.filter_list_of_dicts(response.json()['schedules'], **filters)
def revoke_privilege( self, privilege: Union[str, List[str], "Privilege", List["Privilege"]]) -> None: """Revoke project-level privileges from the Security Role. Args: privilege: list of privilege objects, ids or names """ # get all project level privileges from mstrio.access_and_security.privilege import Privilege project_level = [ priv['id'] for priv in Privilege.list_privileges( self.connection, to_dictionary=True, is_project_level_privilege='True') ] # validate and filter passed privileges privileges = Privilege._validate_privileges(self.connection, privilege) server_level = list( set([priv['id'] for priv in privileges]) - set(project_level)) privileges = helper.filter_list_of_dicts(privileges, id=project_level) # create lists for print purposes privilege_ids = [priv['id'] for priv in privileges] existing_ids = [obj['id'] for obj in self.privileges] succeeded = list(set(privilege_ids).intersection(set(existing_ids))) failed = list(set(privilege_ids) - set(succeeded)) if server_level: msg = ( "Privilege(s) {} are server-level and will be ommited. Only project-level " "privileges can be granted by this method.").format( sorted(server_level)) helper.exception_handler(msg, exception_type=Warning) self._update_nested_properties(objects=privileges, path="privileges", op="removeElement") if succeeded: self.fetch( ) # fetch the object properties and set object attributes if config.verbose: print("Revoked privilege(s) {} from '{}'".format( succeeded, self.name)) elif failed and config.verbose: print("Security Role '{}' does not have privilege(s) {}".format( self.name, failed))
def list_members(self, application_name: str = None): """List all members of the Security Role. Optionally, filter the results by Application name. Args: application_name(str, optional): Application name """ if application_name is not None: [filtered_app] = helper.filter_list_of_dicts(self.projects, name=application_name) members = filtered_app['members'] else: members = [] for project in self.projects: for member in project['members']: members.append(member) return members
def _list_loaded_applications(cls, connection: "Connection", to_dictionary: bool = False, **filters) -> List["Application"]: response = projects.get_projects(connection, whitelist=[('ERR014', 403)]) list_of_dicts = response.json() if response.ok else [] list_of_dicts = helper.camel_to_snake(list_of_dicts) # Convert keys raw_applications = helper.filter_list_of_dicts(list_of_dicts, **filters) if to_dictionary: # return list of application names return raw_applications else: # return list of Application objects return cls._from_bulk_response(connection, raw_applications)
def load_project(node): body = { "operationList": [{ "op": "replace", "path": self._STATUS_PATH, "value": "loaded" }] } response = monitors.update_node_properties(self.connection, node, self.id, body) if response.status_code == 202: tmp = helper.filter_list_of_dicts(self.nodes, name=node) tmp[0]['projects'] = [response.json()['project']] self._nodes = tmp if tmp[0]['projects'][0]['status'] != 'loaded': self.fetch('nodes') if config.verbose: logger.info(f"Project '{self.id}' loaded on node '{node}'.")
def list_members(self, project_name: Optional[str] = None): """List all members of the Security Role. Optionally, filter the results by Project name. Args: project_name(str, optional): Project name """ if project_name is not None: [filtered_project] = helper.filter_list_of_dicts(self.projects, name=project_name) members = filtered_project['members'] else: members = [] for project in self.projects: for member in project['members']: members.append(member) return members
def load_app(node): body = { "operationList": [{ "op": "replace", "path": "/status", "value": "loaded" }] } response = monitors.update_node_properties(self.connection, node, self.id, body) if response.status_code == 202: tmp = helper.filter_list_of_dicts(self.nodes, name=node) tmp[0]['projects'] = [response.json()['project']] self._nodes = tmp if tmp[0]['projects'][0]['status'] != 'loaded': self.fetch('nodes') if config.verbose: print("Application '{}' loaded on node '{}'.".format( self.id, node))
def filter_connections(self, **filters) -> Union[list, None]: """Filter the user connections stored in the `UserConnections` object by specifying the `filters` keyword arguments. Args: **filters: Available filter parameters: ['id', 'parent_id', 'username', 'user_full_name', 'project_index', 'project_id', 'project_name', 'open_jobs_count', 'application_type', 'date_connection_created', 'duration', 'session_id', 'client', 'config_level'] """ filtered_connections = None if not self.user_connections: helper.exception_handler( "Populate the `UserConnections` object with `UserConnections.fetch` first.", Warning) else: filtered_connections = helper.filter_list_of_dicts(self.user_connections, **filters) return filtered_connections
def unload_project(node): body = { "operationList": [{ "op": "replace", "path": self._STATUS_PATH, "value": "unloaded" }] } response = monitors.update_node_properties(self.connection, node, self.id, body, whitelist=[('ERR001', 500)]) if response.status_code == 202: tmp = helper.filter_list_of_dicts(self.nodes, name=node) tmp[0]['projects'] = [response.json()['project']] self._nodes = tmp if tmp[0]['projects'][0]['status'] != 'unloaded': self.fetch('nodes') if config.verbose: logger.info(f"Project '{self.id}' unloaded on node '{node}'.") if response.status_code == 500 and config.verbose: # handle whitelisted logger.warning(f"Project '{self.id}' already unloaded on node '{node}'.")
def idle_app(node, mode): formatted_mode = Application._IDLE_MODE_DICT.get(mode) body = { "operationList": [{ "op": "replace", "path": "/status", "value": formatted_mode }] } response = monitors.update_node_properties(self.connection, node, self.id, body) if response.status_code == 202: tmp = helper.filter_list_of_dicts(self.nodes, name=node) tmp[0]['projects'] = [response.json()['project']] super(Entity, self).__setattr__('nodes', tmp) if tmp[0]['projects'][0]['status'] != formatted_mode: self.fetch('nodes') if config.verbose: print("'{}' changed status to '{}' on node '{}'.".format( self.name, mode, node))
def _validate_recipients(connection, contents: Content, recipients, application_id, delivery_mode): recipients = recipients if isinstance(recipients, list) else [recipients] body = {"contents": contents} available_recipients = subscriptions.available_recipients( connection, application_id, body, delivery_mode) available_recipients = available_recipients.json()['recipients'] available_recipients_ids = [rec['id'] for rec in available_recipients] # Format recipients list if needed formatted_recipients = [] if recipients: for recipient in recipients: if isinstance(recipient, dict): if recipient['id'] in available_recipients_ids: formatted_recipients.append(recipient) else: helper.exception_handler( "'{}' is not a valid recipient ID for selected content and delivery mode. Available recipients: \n{}" .format(recipient['id'], pformat(available_recipients, indent=2)), ValueError) pprint(available_recipients) elif isinstance(recipient, str): if recipient in available_recipients_ids: rec = helper.filter_list_of_dicts(available_recipients, id=recipient) formatted_recipients.append(rec[0]) else: helper.exception_handler( "'{}' is not a valid recipient ID for selected content and delivery mode. Available recipients: \n{}" .format(recipient, pformat(available_recipients, indent=2)), ValueError) pprint(available_recipients) else: helper.exception_handler( "Recipients must be a dictionaries or a strings, not a {}" .format(type(recipient)), exception_type=TypeError) return formatted_recipients
def _list_security_roles( cls, connection: "Connection", to_dictionary: bool = False, to_dataframe: bool = False, **filters ) -> Union[List["SecurityRole"], List[Dict[str, Any]], DataFrame]: if to_dictionary and to_dataframe: helper.exception_handler( "Please select either to_dictionary=True or to_dataframe=True, but not both.", ValueError) response = security.get_security_roles(connection=connection).json() response = helper.camel_to_snake(response) if filters: response = helper.filter_list_of_dicts(response, **filters) if to_dictionary: return response elif to_dataframe: return DataFrame(response) else: return cls._from_bulk_response(connection, response)