From c3e787bebfd9f9f14df186d8c791fb25caafc697 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ale=C5=A1=20Mr=C3=A1zek?= Date: Tue, 28 Feb 2023 18:41:50 +0100 Subject: [PATCH] manager: kresctl: config command refactored --- .../knot_resolver_manager/cli/cmd/config.py | 152 +++++++++++------- manager/knot_resolver_manager/cli/command.py | 13 +- manager/knot_resolver_manager/cli/kresctl.py | 9 +- .../tests/packaging/interactive/workers.sh | 2 +- 4 files changed, 106 insertions(+), 70 deletions(-) diff --git a/manager/knot_resolver_manager/cli/cmd/config.py b/manager/knot_resolver_manager/cli/cmd/config.py index 050ad313e..80a90dd4f 100644 --- a/manager/knot_resolver_manager/cli/cmd/config.py +++ b/manager/knot_resolver_manager/cli/cmd/config.py @@ -93,77 +93,110 @@ def reformat(data: str, req_format: Formats) -> str: class ConfigCommand(Command): def __init__(self, namespace: argparse.Namespace) -> None: super().__init__(namespace) - self.path: str = str(namespace.path) - self.value_or_file: Optional[str] = namespace.value_or_file - self.operation: Operations = namespace.operation - self.format: Formats = namespace.format - self.stdin: bool = namespace.stdin + self.path: str = str(namespace.path) if hasattr(namespace, "path") else "" + self.format: Formats = namespace.format if hasattr(namespace, "format") else Formats.JSON + self.operation: Optional[Operations] = namespace.operation if hasattr(namespace, "operation") else None + self.file: Optional[str] = namespace.file if hasattr(namespace, "file") else None @staticmethod def register_args_subparser( subparser: "argparse._SubParsersAction[argparse.ArgumentParser]", ) -> Tuple[argparse.ArgumentParser, "Type[Command]"]: config = subparser.add_parser("config", help="Performs operations on the running resolver's configuration.") - config.add_argument( - "path", + path_help = "Optional, path (JSON pointer, RFC6901) to the configuration resources. By default, the entire configuration is selected." + + config_subparsers = config.add_subparsers(help="operation type") + + # GET operation + get = config_subparsers.add_parser("get", help="Get current configuration from the resolver.") + get.set_defaults(operation=Operations.GET) + + get.add_argument( + "-p", + "--path", + help=path_help, + action="store", type=str, - help="Path (JSON pointer, RFC6901) to the configuration resources to work with.", + default="", ) - - config.add_argument("--stdin", help="Read config values from stdin.", action="store_true", default=False) - config.add_argument( - "value_or_file", + get.add_argument( + "file", + help="Optional, path to the file where to save exported configuration data. If not specified, data will be printed.", type=str, nargs="?", - help="Optional, new configuration value, path to file with new configuraion or path to file where to save exported configuration data." - "If not specified, the configuration is printed.", - default=None, ) - op_dest = "operation" - operations = config.add_mutually_exclusive_group() - operations.add_argument( - "-s", - "--set", - help="Set new configuration for the resolver.", + get_formats = get.add_mutually_exclusive_group() + get_formats.add_argument( + "--json", + help="Get configuration data in JSON format, default.", + const=Formats.JSON, action="store_const", - dest=op_dest, - const=Operations.SET, - default=Operations.SET, + dest="format", ) - operations.add_argument( - "-d", - "--delete", - help="Delete given configuration property or list item at the given index.", + get_formats.add_argument( + "--yaml", + help="Get configuration data in YAML format.", + const=Formats.YAML, action="store_const", - dest=op_dest, - const=Operations.DELETE, + dest="format", ) - operations.add_argument( - "-g", - "--get", - help="Get current configuration from the resolver.", - action="store_const", - dest=op_dest, - const=Operations.GET, + + # SET operation + set = config_subparsers.add_parser("set", help="Set new configuration for the resolver.") + set.set_defaults(operation=Operations.SET) + + set.add_argument( + "-p", + "--path", + help=path_help, + action="store", + type=str, + default="", ) - fm_dest = "format" - formats = config.add_mutually_exclusive_group() - formats.add_argument( + value_or_file = set.add_mutually_exclusive_group() + value_or_file.add_argument( + "file", + help="Optional, path to file with new configuraion.", + type=str, + nargs="?", + ) + value_or_file.add_argument( + "value", + help="Optional, new configuration value.", + type=str, + nargs="?", + ) + + set_formats = set.add_mutually_exclusive_group() + set_formats.add_argument( "--json", - help="JSON format for input configuration or required format for exported configuration.", - action="store_const", - dest=fm_dest, + help="Set configuration data in JSON format, default.", const=Formats.JSON, - default=Formats.JSON, + action="store_const", + dest="format", ) - formats.add_argument( + set_formats.add_argument( "--yaml", - help="YAML format for input configuration or required format for exported configuration.", - action="store_const", - dest=fm_dest, + help="Set configuration data in YAML format.", const=Formats.YAML, + action="store_const", + dest="format", + ) + + # DELETE operation + delete = config_subparsers.add_parser( + "delete", help="Delete given configuration property or list item at the given index." + ) + delete.set_defaults(operation=Operations.DELETE) + delete.add_argument( + "-p", + "--path", + help=path_help, + action="store", + type=str, + default="", ) return config, ConfigCommand @@ -186,23 +219,24 @@ class ConfigCommand(Command): return {} def run(self, args: CommandArgs) -> None: - if not self.path.startswith("/"): - self.path = "/" + self.path + if not self.operation: + args.subparser.print_help() + sys.exit() new_config = None url = f"{args.socket}/v1/config{self.path}" method = operation_to_method(self.operation) if self.operation == Operations.SET: - # use STDIN also when value_or_file is not specified - if self.stdin or not self.value_or_file: - new_config = input("Type new configuration: ") - else: + if self.file: try: - with open(self.value_or_file, "r") as f: + with open(self.file, "r") as f: new_config = f.read() except FileNotFoundError: - new_config = self.value_or_file + new_config = self.file + else: + # use STDIN also when file is not specified + new_config = input("Type new configuration: ") response = request(method, url, reformat(new_config, Formats.JSON) if new_config else None) @@ -212,9 +246,9 @@ class ConfigCommand(Command): print(f"status: {response.status}") - if self.operation == Operations.GET and self.value_or_file: - with open(self.value_or_file, "w") as f: + if self.operation == Operations.GET and self.file: + with open(self.file, "w") as f: f.write(reformat(response.body, self.format)) - print(f"saved to: {self.value_or_file}") + print(f"saved to: {self.file}") else: print(reformat(response.body, self.format)) diff --git a/manager/knot_resolver_manager/cli/command.py b/manager/knot_resolver_manager/cli/command.py index 4c44b3200..207f91080 100644 --- a/manager/knot_resolver_manager/cli/command.py +++ b/manager/knot_resolver_manager/cli/command.py @@ -27,22 +27,21 @@ def install_commands_parsers(parser: argparse.ArgumentParser) -> None: subparsers = parser.add_subparsers(help="command type") for command in _registered_commands: subparser, typ = command.register_args_subparser(subparsers) - subparser.set_defaults(command=typ) + subparser.set_defaults(command=typ, subparser=subparser) class CommandArgs: def __init__(self, namespace: argparse.Namespace, parser: argparse.ArgumentParser) -> None: + self.namespace = namespace + self.parser = parser + self.subparser: argparse.ArgumentParser = namespace.subparser + self.command: Type["Command"] = namespace.command + self.socket: str = namespace.socket[0] if Path(self.socket).exists(): self.socket = f'http+unix://{quote(self.socket, safe="")}/' if self.socket.endswith("/"): self.socket = self.socket[:-1] - if not hasattr(namespace, "command"): - setattr(namespace, "command", get_help_command()) - - self.command: Type["Command"] = namespace.command - self.parser = parser - self.namespace = namespace class Command(ABC): diff --git a/manager/knot_resolver_manager/cli/kresctl.py b/manager/knot_resolver_manager/cli/kresctl.py index 05f9b445c..cbcc12a34 100644 --- a/manager/knot_resolver_manager/cli/kresctl.py +++ b/manager/knot_resolver_manager/cli/kresctl.py @@ -16,9 +16,12 @@ class Kresctl: self.parser = parser def execute(self): - cmd_args = CommandArgs(self.namespace, self.parser) - command = self.namespace.command(self.namespace) - command.run(cmd_args) + if hasattr(self.namespace, "command"): + args = CommandArgs(self.namespace, self.parser) + command = args.command(self.namespace) + command.run(args) + else: + self.parser.print_help() def _prompt_format(self) -> str: bolt = "\033[1m" diff --git a/manager/tests/packaging/interactive/workers.sh b/manager/tests/packaging/interactive/workers.sh index a0e7fd2cb..cef91b60d 100755 --- a/manager/tests/packaging/interactive/workers.sh +++ b/manager/tests/packaging/interactive/workers.sh @@ -2,6 +2,6 @@ set -e -kresctl config /workers 5 +kresctl config set -p /workers 5 test "$(ps -a -x | grep kresd | grep -v grep | wc -l)" -eq 5 -- 2.47.3