def run(): """ Run the slack application. """ # Get the config. slack_config = config_functions.get_config()['SLACK_APP'] # Get all necessary variables from the config. bot_token = slack_config["slack_bot_token"] app_token = slack_config["slack_app_token"] # This is the id of the channel. channel = slack_config["slack_harmony_channel"] # How often the bot should recheck the channel. watch_time = float(slack_config["watch_time"]) # Maximum number of message senders. max_messengers = int(slack_config["max_messengers"]) # Maximum number of reads from slack when pulling messages. max_reads = int(slack_config["max_reads"]) # Maximum message length to try to read. max_message_length = int(slack_config["max_message_length"]) # Maximum number of keys allowed in any message from slack. max_message_keys = int(slack_config["max_message_keys"]) max_sent_message_length = int(slack_config["max_sent_message_length"]) # Pull the bot token from the os environment and create the connector. app = slack_application.SlackApp(bot_token=bot_token, app_token=app_token, channel=channel, watch_time=watch_time, verbose=1, max_messengers=max_messengers, max_reads=max_reads, max_message_length=max_message_length, max_message_keys=max_message_keys, max_sent_message_length=max_sent_message_length) app.main()
def setUp(self): """ Set the necessary variables for later use. """ # The path to where the sql file for creating the tables is. self.create_tables_path = os.path.join(os.path.dirname(__file__), 'create_test_tables.sql') # The config for the database. database_conf = config_functions.get_config()['CLIENT'] # Initialize a connector. self.connector = connect_database.DatabaseConnector(database_conf)
def run(): """ Run harmony. """ # Create a parser and get the arguments. parser = create_parser() args = parser.parse_args() # Get the written database config. database = config_functions.get_config()['CLIENT'] # Create necessary variables. connector = connect_database.DatabaseConnector(database, user=args.user, password=args.password) rgt_input_paths = [os.path.abspath(path) for path in args.rgt_paths] test_table = database['test_table'] test_event_table = database['test_event_table'] event_table = database['event_table'] # Ignore the warnings for duplicate entries. with warnings.catch_warnings(): warnings.simplefilter("ignore") create_database.create_db(connector) create_database.insert_default(connector) # Continue updating infinitely. while True: print("Updating database.") start = time.time() for rgt_input_path in rgt_input_paths: print("Updating tests in " + rgt_input_path) # Update the database. UD = update_database.UpdateDatabase( connector, rgt_input_path, test_table=test_table, test_event_table=test_event_table, event_table=event_table) try: UD.update_tests() except Exception: traceback.print_exc() del UD total_time = time.time() - start print("Done updating in " + str(total_time) + " seconds.") break # Either immediately refresh if the time it took to update took longer than the refresh time # or sleep for the remaining refresh period. time.sleep(max(int(database['refresh_time']) - total_time, 0))
def slack_notify(message): """ Notify the harmony team using slack. :param message: (str) Message to send. :return: """ conf = config_functions.get_config() app = slack_application.SlackApp( conf['slack_app']['slack_bot_token'], conf['slack_app']['slack_app_token'], conf['slack_app']['slack_harmony_channel']) channel = conf['slack_app']['slack_harmony_channel'] app.send_message(channel=channel, message=message)
return message def dic_print(dic): """ Print a dictionary nicely. :param dic: Dictionary to print. :return: """ m = recursive_print_dic(dic) print(m) if __name__ == '__main__': slack_config = config_functions.get_config()['SLACK_APP'] bot_token = slack_config["slack_bot_token"] app_token = slack_config["slack_app_token"] # This is the id of the channel. channel = slack_config["slack_harmony_channel"] # How often the bot should recheck the channel. watch_time = float(slack_config["watch_time"]) # Maximum number of message senders. max_messengers = int(slack_config["max_messengers"]) # Maximum number of reads from slack when pulling messages. max_reads = int(slack_config["max_reads"]) # Maximum message length to try to read. max_message_length = int(slack_config["max_message_length"]) # Maximum number of keys allowed in any message from slack. max_message_keys = int(slack_config["max_message_keys"])
class MessageParser: """ Class to hold the parser for messages. """ # Get the name of the bot from the config. This is needed so that the documentation on methods is nice # when printed to slack. conf = config_functions.get_config() bot_name = conf['SLACK_APP']['slack_bot_name'] def __init__(self, watch_time=600): # Get all the slack functions in this class. parser_functions = get_functions(MessageParser) # Try to import lsf. If it works then we are on summit. try: from pythonlsf import lsf except ModuleNotFoundError: self.on_summit = False else: self.on_summit = True # Create string for holding descriptions of commants. self.command_descriptions = "" # For each function, add it's name and documentation to the output. for key in sorted(parser_functions): if hasattr(parser_functions[key], 'is_command'): if parser_functions[key].is_command: self.command_descriptions += "*" + key + "*:\t" + parser_functions[ key].__doc__ + "\n\n" # Set how often watching activates. self.watch_time = watch_time # Set up a job monitor for later use. self.JM = job_monitor.JobMonitor() # Initialize the previous path as nothing. self.path_to_previous_rgt = None def parse_message(self, entire_message, slack_sender, channel): """ Take in a message from slack and parse the important info from it and run the correct command. :param entire_message: The response dictionary from the server. :param slack_sender: How to send the response message if using a job monitor. Otherwise it is just a return val. :param channel: Where to send to. :return: The response we make. """ # Get the text of the message and user. message = str(entire_message['text']) slack_user = str(entire_message['user']) # Test if the user is asking for the commands. if 'help' in message: return self.slack_help() # Test if the user wants to see some users jobs. elif 'my_jobs' in message: # Check if on summit. if not self.on_summit: return "I can't seem to find LSF so '" + message + "' won't work." # Split the message. message = message.split() # Get the index of where 'my_jobs' is. The username should be immediately after this. index = message.index('my_jobs') # Try to get the username. If there is no text after it then inform the user that the username is not found. try: username = message[index + 1] except IndexError: return "I can't find your username." # Return the response to searching for that username. return self.my_jobs(username) # Test if the user wants to see all jobs currently running. elif 'all_jobs' in message: if not self.on_summit: return "I can't seem to find LSF so '" + message + "' won't work." return self.all_jobs() # Check if all tests in some path are running. elif 'check_tests' in message: if not self.on_summit: return "I can't seem to find LSF so '" + message + "' won't work." # Split the message and get the path to where their rgt input is. message = message.split() index = message.index('check_tests') try: path = message[index + 1] except IndexError: path = None return self.check_tests(path_to_rgt=path) # Test if a user wants to monitor a job. elif 'monitor_job' in message: if not self.on_summit: return "I can't seem to find LSF so '" + message + "' won't work." # Get all job ids in the message. message = message.split() index = message.index('monitor_job') str_ids = message[(index + 1):] # Try to get the actual ids. If it fails to be an int then return a message. job_ids = [] for str_id in str_ids: try: job_ids.append(int(str_id)) except ValueError: return "I don't understand the a job id that looks like '" + str_id + "'." # If no job ids found in message, inform user. if len(job_ids) == 0: return "I can't find the job id that you wanted to monitor in the message you sent me. \n" + \ "The job may exist, I just can't get the id out of your message." return self.monitor_job(job_ids, slack_sender, channel, slack_user) # If we could not understand the message, tell the user. else: return "I don't understand what exactly you wanted me to do with '" + message + "'. " + self.slack_help( ) # Decorate function so it is labeled as a slack command. @is_command() # Add a parameter to the docstring. @docstring_parameter(bot=bot_name) def slack_help(self): """ Show all commands that I can run. USAGE: @{bot} help :return: This message. """ response = "Here is what I can do! \n\n" + self.command_descriptions return response @is_command() @docstring_parameter(bot=bot_name) def my_jobs(self, username): """ Show all jobs currently running by some user. USAGE: @{bot} my_jobs <username> :param username: Some username. :return: All jobs running by <username>. """ # Import JobStatus so that we can search lsf. from scripts.job_status import JobStatus JS = JobStatus() jobs = JS.get_jobs_by_user(username) # Set up the start of the response. if len(jobs) == 1: response = "I found 1 job by " + username + ".\n" else: response = "I found " + str( len(jobs)) + " jobs by " + username + ".\n" # Create a list of jobs and add headers. tuple_list = [(job.jobId, job.jobName, job.status) for job in jobs] tuple_list.insert(0, ("JOBID", "JOBNAME", "STATUS")) # Make columns and add '```' so that it is correctly formatted. response += "```" + make_columns(tuple_list, col_sizes=[10, 20, 20 ]) + "```" return response @is_command() @docstring_parameter(bot=bot_name) def check_tests(self, path_to_rgt=None): """ Check all harmony tests. A path does not need to be entered. If there is no path, the previous path checked will be used. USAGE: @{bot} check_tests <path> :param path_to_rgt: The path to the rgt_input file that can be accessed by everyone. :return: All tests currently running. """ # Set it to notify by returning the value. returner = lambda x: x # Initialize the message. message = "" # If no path exists in message, check if there was a previous path and if so add it to the message. if path_to_rgt is None: if self.path_to_previous_rgt is None: return "I'm not sure what rgt input you would like me to test." else: message += "I am assuming you want me to recheck " + self.path_to_previous_rgt + ".\n" # If path is in message, use it. elif path_to_rgt is not None: self.path_to_previous_rgt = path_to_rgt # Check the tests in that path. from scripts import test_status return test_status.check_tests(path_to_rgt, notifier=returner) @is_command() @docstring_parameter(bot=bot_name) def monitor_job(self, jobID, slack_sender, channel, slack_user): """ Continue checking on a job and notify when it changes status. USAGE: @{bot} monitor_job <jobID> [<jobID>, ...] :param jobID: The ID of the job to check. :return: An update whenever the job does something in LSF. """ # We need to create a monitor. # Then we pass it in a function that it can call to send out a notification of some sort. def send(user=None, job_id=None, status=None, new_status=None, done=False, error_message=None): message = "<@" + user + "> [" + str(job_id) + "] " if error_message is not None: message += error_message elif new_status is None: if not done: message += "The job is currently " + status + "." else: message += "The job is done with state " + status + "." else: message += "The job has changed from " + status + " to " + new_status + "!" slack_sender.send_message(channel, message) # Remove any dead monitors. self.JM.refresh_threads() # If there are too many monitors, don't try to monitor anothr. if len(self.JM.running_monitors) + 1 > self.JM.max_monitors: return "<@" + slack_user + "> I can't watch this job yet. " \ "I'm already watching " + self.JM.max_monitors + " jobs!" # Start a monitor. self.JM.monitor_jobs(job_ids=jobID, watch_time=self.watch_time, notifier=send, user=slack_user) # Inform the user that the message has been recieved. return "I have started monitoring " + str(jobID) + "." @is_command() @docstring_parameter(bot=bot_name) def all_jobs(self): """ Show all jobs currently on LSF. :return: All jobs. """ # Get all jobs from lsf. from scripts.job_status import JobStatus JS = JobStatus() jobs = JS.get_jobs() # Set up the beginning of the response. if len(jobs) == 1: response = "I found 1 job in LSF.\n" else: response = "I found " + str(len(jobs)) + " jobs in LSF.\n" # Create a list of jobs. tuple_list = [(job.jobId, job.user, job.jobName, job.status) for job in jobs] # Add headers to message. tuple_list.insert(0, ("JOBID", "USER", "JOBNAME", "STATUS")) # Format the message and send it back. response += "```" + make_columns(tuple_list, [10, 10, 20, 20]) + "```" return response