def rt_client_session(self): # This method is made public to make it possible to operate # on an `rt.Rt` instance directly. However, if possible, it # is recommended to use other `RTClientAPI`'s public methods # rather than this one. if not self._config['active']: raise RuntimeError('RT is turn off in the configuration ' '(i.e., the `active` option is false)') rest_api_url = self._config['rest_api_url'] username = self._config['username'] try: rt = Rt(url=rest_api_url) if not rt.login(login=username, password=self._config['password']): raise RTClientAPIError('could not log in to RT') try: yield rt finally: self._try_logout(rt) except (RtError, RTClientAPIError) as exc: raise RTClientAPIError('RT-related error - {} (' 'config option {}: {!r}; ' 'config option {}: {!r})'.format( make_exc_ascii_str(exc), self._repr_opt_name('rest_api_url'), rest_api_url, self._repr_opt_name('username'), username))
def connect(self, timeout=60): if not self._config: print("Making dummy connection - all operations will be no-ops.", file=sys.stderr) return self self.server_path = self._config['server'] self.username, self.password = self._config['user'], self._config['pass'] self._queue = self._config[self._queue_setting] self.tracker = Rt( '/'.join([self.server_path, 'REST', '1.0']), self.username, self.password, default_queue = self._queue ) if not self.tracker.login(): raise AuthorizationError('login() failed on {_config_name} ({tracker.url})'.format(**vars(self))) # Here comes the big monkey-patch-o-doom! # It will force a 60-second timeout on the Rt session, assuming the internal implementation # of session is not changed in the requests library. from types import MethodType foo = self.tracker.session foo._merge_environment_settings = foo.merge_environment_settings foo.merge_environment_settings = MethodType( lambda s, *a: dict([*s._merge_environment_settings(*a).items(), ('timeout', s.timeout)]), foo ) foo.timeout = timeout # End of monkey business return self
def get_instance(): """Make a RT instance and return it.""" url = current_app.config.get("CFG_BIBCATALOG_SYSTEM_RT_URL", "") login = current_app.config.get("CFG_BIBCATALOG_SYSTEM_RT_DEFAULT_USER", "") password = current_app.config.get("CFG_BIBCATALOG_SYSTEM_RT_DEFAULT_PWD", "") if url: tracker = Rt( url=url, default_login=login, default_password=password, ) tracker.login() return tracker
class RTManager(): def __init__(self, config_name, queue_setting): """Communication with RT is managed via the RT module. This wrapper picks up connection params from an .ini file, which must exist before you can even instatiate the object. To actually connect, either call connect() explicitly or say: with RTManager('test-rt') as rt_conn: ... to connect implicitly. """ self._config_name = config_name self._queue_setting = queue_setting + '_queue' # eg. pbrun_queue, run_queue if config_name.lower() == 'none': # Special case for short-circuiting RT entirely, whatever the .ini # file says. self._config = None else: self._config = self._get_config_from_ini(config_name) self.tracker = None def connect(self, timeout=60): if not self._config: print("Making dummy connection - all operations will be no-ops.", file=sys.stderr) return self self.server_path = self._config['server'] self.username, self.password = self._config['user'], self._config['pass'] self._queue = self._config[self._queue_setting] self.tracker = Rt( '/'.join([self.server_path, 'REST', '1.0']), self.username, self.password, default_queue = self._queue ) if not self.tracker.login(): raise AuthorizationError('login() failed on {_config_name} ({tracker.url})'.format(**vars(self))) # Here comes the big monkey-patch-o-doom! # It will force a 60-second timeout on the Rt session, assuming the internal implementation # of session is not changed in the requests library. from types import MethodType foo = self.tracker.session foo._merge_environment_settings = foo.merge_environment_settings foo.merge_environment_settings = MethodType( lambda s, *a: dict([*s._merge_environment_settings(*a).items(), ('timeout', s.timeout)]), foo ) foo.timeout = timeout # End of monkey business return self # Allow us to use this in a 'with' clause. def __enter__(self): return self.connect() def __exit__(self, *exc): # Can you logout of RT? Do you want to? pass def _get_config_from_ini(self, section_name): # Either read the config pointed to by RT_SETTINGS or else the default. # Don't attempt to read both, even though ConfigParser supports it. file_name = os.environ.get('RT_SETTINGS') file_name = file_name or os.path.join(os.path.expanduser('~'), '.rt_settings') cp = configparser.ConfigParser() if not cp.read(file_name): raise AuthorizationError('unable to read configuration file {file_name}'.format(**locals())) # A little validation if section_name not in cp: raise AuthorizationError('file {file_name} contains no configuration section {section_name}'.format(**locals())) conf_section = cp[section_name] # A little more validation if not all([conf_section.get(x) for x in ['server', 'user', 'pass', self._queue_setting]]): raise AuthorizationError('file {file_name} did not contain all settings needed for RT authentication'.format(**locals())) return conf_section # Added for illuminatus, adapted for SMRTino def find_or_create_run_ticket(self, run_id, subject, text=None): """Create a ticket for run if it does not exist already. If text is specified it will be used as the request blurb for the new ticket but if the ticket already existed it will be ignored. Returns a pair (ticket_id, created?). """ c = self._config ticket_id, _ = self.search_run_ticket(run_id) if ticket_id: return ticket_id, False # Since dummy mode returns 999, if ticket_id was unset we can infer we have a real # connection and proceed with real ops. # Text munge text = re.sub(r'\n', r'\n ', text.rstrip()) if text \ else "" ticket_id = int(self.tracker.create_ticket( Subject = subject, Queue = self._queue, Requestor = c['requestor'], Cc = c.get('run_cc'), Text = text or "" )) # Open the ticket, or we'll not find it again. self.tracker.edit_ticket(ticket_id, Status='open') return ticket_id, True def search_run_ticket(self, run_id): """Search for a ticket referencing this run, and return the ticket number, as an integer, along with the ticket metadata as a dict, or return (None, None) if there is no such ticket. """ c = self._config if not c: #In dummy mode, all tickets are 999 return (999, dict()) # Note - if the tickets aren't opened then 'new' tickets will just pile up in RT, # but I don't think that should happen. tickets = list(self.tracker.search( Queue = self._queue, Subject__like = '%{}%'.format(run_id), Status = 'open' )) if not tickets: return (None, None) # Order the tickets by tid and get the highest one def get_id(t): return int(t.get('id').strip('ticket/')) tickets.sort(key=get_id, reverse=True) tid = get_id(tickets[0]) if len(tickets) > 1: # Should use really use proper logging here print("Warning: We have {} open tickets for run {}! Using the latest, {}".format( len(tickets), run_id, tid), file=sys.stderr) #Failing that... return (tid, tickets[0]) if tid > 0 else (None, None) def reply_to_ticket(self, ticket_id, message, subject=None): """Sends a reply to the ticket. """ if subject: # The rest API does not support supplying a subject, but I can maybe # hack around this? No, not easily. raise NotImplementedError("RT REST API does not support setting subjects on replies.") # Dummy connection mode... if not self._config: return True return self.tracker.reply(ticket_id, text=message) def comment_on_ticket(self, ticket_id, message, subject=None): if subject: #The rest API does not support supplying a subject, but I can maybe #hack around this? No, not easily. raise NotImplementedError("RT REST API does not support setting subjects on replies.") # Dummy connection mode... if not self._config: return True return self.tracker.comment(ticket_id, text=message) def change_ticket_status(self, ticket_id, status): # Dummy connection mode... if not self._config: return kwargs = dict( Status = status ) # Ignore IndexError raised when subject is already set with suppress(IndexError): self.tracker.edit_ticket(ticket_id, **kwargs) def change_ticket_subject(self, ticket_id, subject): """You can reply to a ticket with a one-off subject, but not via the REST interface, which fundamentally does not support this. (see share/html/REST/1.0/Forms/ticket/comment in the RT source code) This call permanently changes the ticket subject. """ # Dummy connection mode... if not self._config: return # why the extra space?? I'm not sure but it looks to have been added deliberately. kwargs = dict( Subject = "{} ".format(subject) ) # Ignore IndexError raised when subject is already set with suppress(IndexError): self.tracker.edit_ticket(ticket_id, **kwargs)
def create_rt_ticket(self, observable): # create an observable config item that will be used to contain all observable and template info # to pass along to RT during ticket creation obs_config = RT4ResponderConfig(weight='case', **self.config) obs_config.update(weight='observable', **observable) # defang indicators and write them back as a single string joined together by newlines if 'indicator_list' in observable: indicator_list = defang(u'\n'.join(observable['indicator_list'])) observable.update(weight='observable', **{'indicator_list': indicator_list}) else: self.error("""Unable to find indicators on case/alert/observable: {}""".format(json.dumps(observable, indent=1))) if 'template' in observable: obs_config.update(weight='observable', **{'template': observable['template']}) if 'template' not in obs_config: self.error(""" Couldn't map a tag to a notification type. Observable/alert/case must be tagged with one 'rt4_set_template:<template_name>' tag, where <template_name> is the name of a file (without .j2 ext) in /templates dir""" ) # render the notification template to be passed on to the observable config item rendered_template = NotificationContext().render_blocks_to_dict( template_name=obs_config['template'], kwargs=observable) obs_config.update(weight='template', **rendered_template) if 'Requestor' in observable: obs_config.update(weight='observable', **{'Requestor': observable['Requestor']}) if 'Requestor' not in obs_config: self.error(""" Case/alert/observable must be tagged with at least one 'contact:[email protected]' or set_rt4_requestor:[email protected] tag with an appropriate email address""" ) # build session dict rt_session = { 'url': self.server + "/REST/1.0/", 'default_login': self.username, 'default_password': self.password } # create ticket dict rt_ticket = {} # add additional k,v pairs (as long as it's not the template or indicator_list since those are not accepted # as params to the Rt py module for RT ticket creation) for key, value in obs_config.items(): if obs_config[ key] is not None and key != 'indicator_list' and key != 'template': rt_ticket[key] = value # create rt session try: rt_session = Rt(**rt_session) login_ret = rt_session.login() except ConnectionError as e: self.error("{}".format(e)) except Exception as e: self.error("Error: {}".format(e)) if login_ret != True: self.error('Authentication/Connection error to RT') # create ticket try: new_ticket = rt_session.create_ticket(**rt_ticket) except Exception as e: rt_session.logout() self.error( """RT ticket creation error: {} Possibly bad data such as non-existent Owner or Queue; or data that does not correspond to an RT field. \nSent the following RT request: {}""".format( e, json.dumps(rt_ticket, indent=2))) rt_session.logout() return new_ticket, rt_ticket
if user: kwargs["on_behalf_of"] = user ZAMMADS[user] = ZammadAPI( host=config["zammad_host"], username=config["zammad_user"], password=config["zammad_password"], is_secure=config["zammad_secure"], **kwargs, ) return ZAMMADS[user] target = get_zammad() target.user.me() source = Rt(config["rt_url"], config["rt_user"], config["rt_pass"]) if not source.login(): print("Failed to login to RT!") sys.exit(2) if os.path.exists("rt2zammad.cache"): # Load RT from cache with open("rt2zammad.cache", "rb") as handle: data = pickle.load(handle) users = data["users"] queues = data["queues"] tickets = data["tickets"] attachments = data["attachments"] else: # Load RT from remote
# -*- coding: utf-8 -*- import sys from PyQt5.QtWidgets import QApplication from rt import Rt from rt.article import Article if __name__ == '__main__': app = QApplication(sys.argv) rt = Rt() rt.show() sys.exit(app.exec_()) # a = Article() # print(a.list()) # a = Article() # print(a.item('123'))
def tree(self, test_no): tree = Rt(self.provider) return tree.test(test_no)
def get_tracker(self): return Rt(settings.RT_HOST, settings.RT_UN, settings.RT_PW, http_auth=HTTPBasicAuth(settings.RT_UN, settings.RT_PW))