class Context(object): def __init__(self): self.hostname = None self.connection = Client() self.ml = None self.logger = logging.getLogger('cli') self.plugin_dirs = [] self.task_callbacks = {} self.plugins = {} self.variables = VariableStore() self.root_ns = RootNamespace('') self.event_masks = ['*'] self.event_divert = False self.event_queue = six.moves.queue.Queue() self.keepalive_timer = None self.argparse_parser = None config.instance = self @property def is_interactive(self): return os.isatty(sys.stdout.fileno()) def start(self): self.discover_plugins() self.connect() def connect(self): try: self.connection.connect(self.hostname) except socket_error as err: output_msg(_( "Could not connect to host: {0} due to error: {1}".format(self.hostname, err) )) self.argparse_parser.print_help() sys.exit(1) def login(self, user, password): try: self.connection.login_user(user, password) self.connection.subscribe_events(*EVENT_MASKS) self.connection.on_event(self.handle_event) self.connection.on_error(self.connection_error) except RpcException as e: if e.code == errno.EACCES: self.connection.disconnect() output_msg(_("Wrong username or password")) sys.exit(1) self.login_plugins() def keepalive(self): if self.connection.opened: self.connection.call_sync('management.ping') def read_middleware_config_file(self, file): """ If there is a cli['plugin-dirs'] in middleware.conf use that, otherwise use the default plugins dir within cli namespace """ plug_dirs = None if file: with open(file, 'r') as f: data = json.load(f) if 'cli' in data and 'plugin-dirs' in data['cli']: if type(data['cli']['plugin-dirs']) != list: return self.plugin_dirs += data['cli']['plugin-dirs'] if plug_dirs is None: plug_dirs = os.path.dirname(os.path.realpath(__file__)) plug_dirs = os.path.join(plug_dirs, 'plugins') self.plugin_dirs += [plug_dirs] def discover_plugins(self): for dir in self.plugin_dirs: self.logger.debug(_("Searching for plugins in %s"), dir) self.__discover_plugin_dir(dir) def login_plugins(self): for i in list(self.plugins.values()): if hasattr(i, '_login'): i._login(self) def __discover_plugin_dir(self, dir): for i in glob.glob1(dir, "*.py"): self.__try_load_plugin(os.path.join(dir, i)) def __try_load_plugin(self, path): if path in self.plugins: return self.logger.debug(_("Loading plugin from %s"), path) name, ext = os.path.splitext(os.path.basename(path)) plugin = imp.load_source(name, path) if hasattr(plugin, '_init'): plugin._init(self) self.plugins[path] = plugin def __try_reconnect(self): output_lock.acquire() self.ml.blank_readline() output_msg(_('Connection lost! Trying to reconnect...')) retries = 0 while True: retries += 1 try: time.sleep(2) self.connect() try: if self.hostname == '127.0.0.1': self.connection.login_user(getpass.getuser(), '') else: self.connection.login_token(self.connection.token) self.connection.subscribe_events(*EVENT_MASKS) except RpcException: output_msg(_("Reauthentication failed (most likely token expired or server was restarted)")) sys.exit(1) break except Exception as e: output_msg(_('Cannot reconnect: {0}'.format(str(e)))) self.ml.restore_readline() output_lock.release() def attach_namespace(self, path, ns): splitpath = path.split('/') ptr = self.root_ns ptr_namespaces = ptr.namespaces() for n in splitpath[1:-1]: if n not in list(ptr_namespaces().keys()): self.logger.warn(_("Cannot attach to namespace %s"), path) return ptr = ptr_namespaces()[n] ptr.register_namespace(ns) def connection_error(self, event, **kwargs): if event == ClientError.LOGOUT: output_msg('Logged out from server.') self.connection.disconnect() sys.exit(0) if event == ClientError.CONNECTION_CLOSED: time.sleep(1) self.__try_reconnect() return def handle_event(self, event, data): if event == 'task.updated': if data['id'] in self.task_callbacks: self.handle_task_callback(data) self.print_event(event, data) def handle_task_callback(self, data): if data['state'] in ('FINISHED', 'CANCELLED', 'ABORTED', 'FAILED'): self.task_callbacks[data['id']](data['state']) def print_event(self, event, data): if self.event_divert: self.event_queue.put((event, data)) return if event == 'task.progress': return output_lock.acquire() self.ml.blank_readline() translation = events.translate(self, event, data) if translation: output_msg(translation) if 'state' in data: if data['state'] == 'FAILED': status = self.connection.call_sync('task.status', data['id']) output_msg(_( "Task #{0} error: {1}".format( data['id'], status['error'].get('message', '') if status.get('error') else '' ) )) sys.stdout.flush() self.ml.restore_readline() output_lock.release() def call_sync(self, name, *args, **kwargs): return wrap(self.connection.call_sync(name, *args, **kwargs)) def call_task_sync(self, name, *args, **kwargs): self.ml.skip_prompt_print = True wrapped_result = wrap(self.connection.call_task_sync(name, *args)) self.ml.skip_prompt_print = False return wrapped_result def submit_task(self, name, *args, **kwargs): callback = kwargs.pop('callback', None) message_formatter = kwargs.pop('message_formatter', None) if not self.variables.get('tasks_blocking'): tid = self.connection.call_sync('task.submit', name, args) if callback: self.task_callbacks[tid] = callback return tid else: output_msg(_("Hit Ctrl+C to terminate task if needed")) self.event_divert = True tid = self.connection.call_sync('task.submit', name, args) progress = ProgressBar() try: while True: event, data = self.event_queue.get() if event == 'task.progress' and data['id'] == tid: message = data['message'] if isinstance(message_formatter, collections.Callable): message = message_formatter(message) progress.update(percentage=data['percentage'], message=message) if event == 'task.updated' and data['id'] == tid: progress.update(message=data['state']) if data['state'] == 'FINISHED': progress.finish() break if data['state'] == 'FAILED': print() break except KeyboardInterrupt: print() output_msg(_("User requested task termination. Task abort signal sent")) self.call_sync('task.abort', tid) self.event_divert = False return tid