def __call__(self, parser, namespace, values, option_string=None): parser_name = values[0] try: # Check for deprecated command name correct_name = self._deprecated_command_map[parser_name] except KeyError: pass else: # Warn the user about deprecated command if correct_name is None: logger.warning( m18n.g("deprecated_command", prog=parser.prog, command=parser_name) ) else: logger.warning( m18n.g( "deprecated_command_alias", old=parser_name, new=correct_name, prog=parser.prog, ) ) values[0] = correct_name return super(_ExtendedSubParsersAction, self).__call__( parser, namespace, values, option_string )
def display(self, message, style="info"): # i18n: info """Display a message""" if style == "success": print("{} {}".format(colorize(m18n.g("success"), "green"), message)) elif style == "warning": print("{} {}".format(colorize(m18n.g("warning"), "yellow"), message)) elif style == "error": print("{} {}".format(colorize(m18n.g("error"), "red"), message)) else: print(message)
def logout(self): profile = request.params.get("profile", self.actionsmap.default_authentication) authenticator = self.actionsmap.get_authenticator(profile) try: authenticator.get_session_cookie() except KeyError: raise HTTPResponse(m18n.g("not_logged_in"), 401) else: # Delete cookie and clean the session authenticator.delete_session_cookie() return m18n.g("logged_out")
def login(self): """Log in to an authenticator Attempt to authenticate to the default authenticator and register it with the current session - a new one will be created if needed. """ if "credentials" not in request.params: raise HTTPResponse("Missing credentials parameter", 400) credentials = request.params["credentials"] profile = request.params.get("profile", self.actionsmap.default_authentication) authenticator = self.actionsmap.get_authenticator(profile) try: auth_infos = authenticator.authenticate_credentials(credentials) except MoulinetteError as e: try: self.logout() except Exception: pass raise HTTPResponse(e.strerror, 401) else: authenticator.set_session_cookie(auth_infos) return m18n.g("logged_in")
def __call__(self, arguments, arg_name, arg_value): pattern, message = (arguments[0], arguments[1]) # Use temporarly utf-8 encoded value try: v = str(arg_value, "utf-8") except Exception: v = arg_value if v and not re.match(pattern, v or "", re.UNICODE): logger.warning( "argument value '%s' for '%s' doesn't match pattern '%s'", v, arg_name, pattern, ) # Attempt to retrieve message translation msg = m18n.n(message) if msg == message: msg = m18n.g(message) raise MoulinetteValidationError( "invalid_argument", argument=arg_name, error=msg ) return arg_value
def tools_maindomain(new_main_domain=None): from yunohost.domain import domain_main_domain logger.warning( m18n.g("deprecated_command_alias", prog="yunohost", old="tools maindomain", new="domain main-domain")) return domain_main_domain(new_main_domain=new_main_domain)
def test_read_file_missing_file(): bad_file = "doesnt-exist" with pytest.raises(MoulinetteError) as exception: read_file(bad_file) translation = m18n.g("file_not_exist", path=bad_file) expected_msg = translation.format(path=bad_file) assert expected_msg in str(exception)
def authenticate(self, authenticator): try: session_infos = authenticator.get_session_cookie() except Exception: msg = m18n.g("authentication_required") raise HTTPResponse(msg, 401) return session_infos
def test_required_paremeter_missing_value(iface, caplog): required = RequiredParameter(iface) with pytest.raises(MoulinetteError) as exception: required(True, "a", "") translation = m18n.g("argument_required", argument="a") expected_msg = translation.format(argument="a") assert expected_msg in str(exception) assert any("is required" in message for message in caplog.messages)
def authenticate(self, authenticator): # Hmpf we have no-use case in yunohost anymore where we need to auth # because everything is run as root ... # I guess we could imagine some yunohost-independant use-case where # moulinette is used to create a CLI for non-root user that needs to # auth somehow but hmpf -.- msg = m18n.g("password") credentials = self.prompt(msg, True, False, color="yellow") return authenticator.authenticate_credentials(credentials=credentials)
def _prompt(message): if not is_multiline: import prompt_toolkit from prompt_toolkit.completion import WordCompleter from prompt_toolkit.styles import Style autocomplete_ = WordCompleter(autocomplete) style = Style.from_dict({ "": "", "message": f"#ansi{color} bold", }) if help: def bottom_toolbar(): return [("class:", help)] else: bottom_toolbar = None colored_message = [ ("class:message", message), ("class:", ": "), ] return prompt_toolkit.prompt( colored_message, bottom_toolbar=bottom_toolbar, style=style, default=prefill, completer=autocomplete_, complete_while_typing=True, is_password=is_password, ) else: while True: value = input( colorize(m18n.g("edit_text_question", message), color)) value = value.lower().strip() if value in ["", "n", "no"]: return prefill elif value in ["y", "yes"]: break initial_message = prefill.encode("utf-8") with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: tf.write(initial_message) tf.flush() call(["editor", tf.name]) tf.seek(0) edited_message = tf.read() return edited_message.decode("utf-8")
def test_read_yaml_cannot_read(test_yaml, mocker): error = "foobar" mocker.patch("yaml.safe_load", side_effect=Exception(error)) with pytest.raises(MoulinetteError) as exception: read_yaml(str(test_yaml)) translation = m18n.g("corrupted_yaml", ressource=str(test_yaml), error=error) expected_msg = translation.format(ressource=str(test_yaml), error=error) assert expected_msg in str(exception)
def test_read_file_cannot_read_exception(test_file, mocker): error = "foobar" mocker.patch("builtins.open", side_effect=Exception(error)) with pytest.raises(MoulinetteError) as exception: read_file(str(test_file)) translation = m18n.g("unknown_error_reading_file", file=str(test_file), error=error) expected_msg = translation.format(file=str(test_file), error=error) assert expected_msg in str(exception)
def test_remove_file_bad_perms(test_file, mocker): error = "foobar" mocker.patch("os.remove", side_effect=OSError(error)) with pytest.raises(MoulinetteError) as exception: rm(str(test_file)) translation = m18n.g("error_removing", path=str(test_file), error=error) expected_msg = translation.format(path=str(test_file), error=error) assert expected_msg in str(exception)
def test_write_to_yaml_bad_perms(test_yaml, mocker): error = "foobar" mocker.patch("builtins.open", side_effect=IOError(error)) with pytest.raises(MoulinetteError) as exception: write_to_yaml(str(test_yaml), {"a": 1}) translation = m18n.g("cannot_write_file", file=str(test_yaml), error=error) expected_msg = translation.format(file=str(test_yaml), error=error) assert expected_msg in str(exception)
def test_write_to_file_exception(test_file, mocker): error = "foobar" mocker.patch("builtins.open", side_effect=Exception(error)) with pytest.raises(MoulinetteError) as exception: write_to_file(str(test_file), "yolo\nswag") translation = m18n.g("error_writing_file", file=str(test_file), error=error) expected_msg = translation.format(file=str(test_file), error=error) assert expected_msg in str(exception)
def test_read_json_cannot_read(test_json, mocker): error = "foobar" mocker.patch("json.loads", side_effect=ValueError(error)) with pytest.raises(MoulinetteError) as exception: read_json(str(test_json)) translation = m18n.g("corrupted_json", ressource=str(test_json), error=error) expected_msg = translation.format(ressource=str(test_json), error=error) assert expected_msg in str(exception)
def test_write_yaml_to_file_exception(test_file, mocker): error = "foobar" dummy_dict = {"foo": 42, "bar": ["a", "b", "c"]} mocker.patch("builtins.open", side_effect=Exception(error)) with pytest.raises(MoulinetteError) as exception: write_to_yaml(str(test_file), dummy_dict) translation = m18n.g("error_writing_file", file=str(test_file), error=error) expected_msg = translation.format(file=str(test_file), error=error) assert expected_msg in str(exception)
def test_chmod_exception(test_file, mocker): error = "foobar" mocker.patch("os.chmod", side_effect=Exception(error)) with pytest.raises(MoulinetteError) as exception: chmod(str(test_file), 0o000) translation = m18n.g( "error_changing_file_permissions", path=str(test_file), error=str(error) ) expected_msg = translation.format(path=str(test_file), error=str(error)) assert expected_msg in str(exception)
def test_chown(test_file): with pytest.raises(ValueError): chown(str(test_file)) current_uid = os.getuid() current_gid = os.getgid() chown(str(test_file), current_uid, current_gid) assert os.stat(str(test_file)).st_uid == current_uid assert os.stat(str(test_file)).st_gid == current_gid current_gid = os.getgid() chown(str(test_file), uid=None, gid=current_gid) assert os.stat(str(test_file)).st_gid == current_gid current_uid = pwd.getpwuid(os.getuid())[0] current_gid = grp.getgrgid(os.getgid())[0] chown(str(test_file), current_uid, current_gid) assert os.stat(str(test_file)).st_uid == os.getuid() assert os.stat(str(test_file)).st_gid == os.getgid() fake_user = "******" with pytest.raises(MoulinetteError) as exception: chown(str(test_file), fake_user) translation = m18n.g("unknown_user", user=fake_user) expected_msg = translation.format(user=fake_user) assert expected_msg in str(exception) fake_grp = "nogrplol" with pytest.raises(MoulinetteError) as exception: chown(str(test_file), gid=fake_grp) translation = m18n.g("unknown_group", group=fake_grp) expected_msg = translation.format(group=fake_grp) assert expected_msg in str(exception)
def check_command_is_valid_before_postinstall(args): allowed_if_not_postinstalled = [ 'tools postinstall', 'tools versions', 'backup list', 'backup restore', 'log display' ] if (len(args) < 2 or (args[0] + ' ' + args[1] not in allowed_if_not_postinstalled)): init_i18n() print( colorize(m18n.g('error'), 'red') + " " + m18n.n('yunohost_not_installed')) sys.exit(1)
def test_pattern_parameter(iface, caplog, mocker): pattern = PatternParameter(iface) arg = pattern(["foo", "foobar"], "foo_name", "foo_value") assert arg == "foo_value" error = "error_message" mocker.patch("moulinette.Moulinette18n.n", return_value=error) with pytest.raises(MoulinetteError) as exception: pattern(["foo", "message"], "foo_name", "not_match") translation = m18n.g("invalid_argument", argument="foo_name", error=error) expected_msg = translation.format(argument="foo_name", error=error) assert expected_msg in str(exception) assert any("doesn't match pattern" in message for message in caplog.messages)
def check_command_is_valid_before_postinstall(args): allowed_if_not_postinstalled = [ "tools postinstall", "tools versions", "tools shell", "backup list", "backup restore", "log display", ] if len(args) < 2 or (args[0] + " " + args[1] not in allowed_if_not_postinstalled): init_i18n() print(colorize(m18n.g("error"), "red") + " " + m18n.n("yunohost_not_installed")) sys.exit(1)
def mkdir(path, mode=0o0777, parents=False, uid=None, gid=None, force=False): """Create a directory with optional features Create a directory and optionaly set its permissions to mode and its owner and/or group. If path refers to an existing path, nothing is done unless force is True. Keyword arguments: - path -- The directory to create - mode -- Numeric path mode to set - parents -- Make parent directories as needed - uid -- Numeric uid or user name - gid -- Numeric gid or group name - force -- Force directory creation and owning even if the path exists """ if os.path.exists(path) and not force: raise OSError(errno.EEXIST, m18n.g("folder_exists", path=path)) if parents: # Create parents directories as needed head, tail = os.path.split(path) if not tail: head, tail = os.path.split(head) if head and tail and not os.path.exists(head): try: mkdir(head, mode, parents, uid, gid, force) except OSError as e: if e.errno != errno.EEXIST: raise if tail == os.curdir: return # Create directory and set permissions try: oldmask = os.umask(000) os.mkdir(path, mode) os.umask(oldmask) except OSError: # mimic Python3.2+ os.makedirs exist_ok behaviour if not force or not os.path.isdir(path): raise if uid is not None or gid is not None: chown(path, uid, gid)
def format(self, record): """Enhance message with level and colors if supported.""" msg = record.getMessage() if self.supports_color(): level = "" if self.level <= log.DEBUG: # add level name before message level = "%s " % record.levelname elif record.levelname in ["SUCCESS", "WARNING", "ERROR", "INFO"]: # add translated level name before message level = "%s " % m18n.g(record.levelname.lower()) color = self.LEVELS_COLOR.get(record.levelno, "white") msg = "{}{}{}{}".format(colors_codes[color], level, END_CLI_COLOR, msg) if self.formatter: # use user-defined formatter record.__dict__[self.message_key] = msg return self.formatter.format(record) return msg
def messages(self): """Listen to the messages WebSocket stream Retrieve the WebSocket stream and send to it each messages displayed by the display method. They are JSON encoded as a dict { style: message }. """ profile = request.params.get("profile", self.actionsmap.default_authentication) authenticator = self.actionsmap.get_authenticator(profile) s_id = authenticator.get_session_cookie()["id"] try: queue = self.log_queues[s_id] except KeyError: # Create a new queue for the session queue = Queue() self.log_queues[s_id] = queue wsock = request.environ.get("wsgi.websocket") if not wsock: raise HTTPResponse(m18n.g("websocket_request_expected"), 500) while True: item = queue.get() try: # Retrieve the message style, message = item except TypeError: if item == StopIteration: # Delete the current queue and break del self.log_queues[s_id] break logger.exception("invalid item in the messages queue: %r", item) else: try: # Send the message wsock.send(json_encode({style: message})) except WebSocketError: break sleep(0)
def prompt( self, message, is_password=False, confirm=False, color="blue", prefill="", is_multiline=False, autocomplete=[], help=None, ): """Prompt for a value Keyword arguments: - color -- The color to use for prompting message """ if not os.isatty(1): raise MoulinetteError("Not a tty, can't do interactive prompts", raw_msg=True) def _prompt(message): if not is_multiline: import prompt_toolkit from prompt_toolkit.completion import WordCompleter from prompt_toolkit.styles import Style autocomplete_ = WordCompleter(autocomplete) style = Style.from_dict({ "": "", "message": f"#ansi{color} bold", }) if help: def bottom_toolbar(): return [("class:", help)] else: bottom_toolbar = None colored_message = [ ("class:message", message), ("class:", ": "), ] return prompt_toolkit.prompt( colored_message, bottom_toolbar=bottom_toolbar, style=style, default=prefill, completer=autocomplete_, complete_while_typing=True, is_password=is_password, ) else: while True: value = input( colorize(m18n.g("edit_text_question", message), color)) value = value.lower().strip() if value in ["", "n", "no"]: return prefill elif value in ["y", "yes"]: break initial_message = prefill.encode("utf-8") with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: tf.write(initial_message) tf.flush() call(["editor", tf.name]) tf.seek(0) edited_message = tf.read() return edited_message.decode("utf-8") value = _prompt(message) if confirm: m = message[0].lower() + message[1:] if _prompt(m18n.g("confirm", prompt=m)) != value: raise MoulinetteValidationError("values_mismatch") return value