]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
manager: kresctl: config command refactored
authorAleš Mrázek <ales.mrazek@nic.cz>
Tue, 28 Feb 2023 17:41:50 +0000 (18:41 +0100)
committerAleš Mrázek <ales.mrazek@nic.cz>
Thu, 2 Mar 2023 10:39:46 +0000 (11:39 +0100)
manager/knot_resolver_manager/cli/cmd/config.py
manager/knot_resolver_manager/cli/command.py
manager/knot_resolver_manager/cli/kresctl.py
manager/tests/packaging/interactive/workers.sh

index 050ad313e1966f00ad08d88c8de19835c6476f58..80a90dd4f5a39f81f870c870f344ec459a95ce5a 100644 (file)
@@ -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))
index 4c44b32008d575828f18ac9ee8af4d6c91dd7b4a..207f910802d440f8c9a82e5e4aecf066f46c82c4 100644 (file)
@@ -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):
index 05f9b445c6d33fa9a0007c9d1eb906740151922c..cbcc12a34930e7c9eefc96f12fb32cc5a673fbfb 100644 (file)
@@ -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"
index a0e7fd2cb55072cb5e152f70026a35580de9abc5..cef91b60d2408cad0779317fb403154a60a3546d 100755 (executable)
@@ -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