Skip to content

Commit 11e2cf0

Browse files
committed
Added the core of QMCTL
This commits adds the main function the init of the subcommand and the handle function for the subcommand with other vital function. Signed-off-by: Artiom Divak <[email protected]>
1 parent 2202495 commit 11e2cf0

File tree

1 file changed

+351
-0
lines changed

1 file changed

+351
-0
lines changed

tools/qmctl/qmctl

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,3 +731,354 @@ class SubcommandInitializer:
731731
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
732732
# End of the SubcommandInitializer class
733733
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
734+
735+
def perror(*args, **kwargs):
736+
"""Print a message to the standard error stream."""
737+
print(*args, file=sys.stderr, **kwargs)
738+
739+
740+
def handle_error(e, exit_code):
741+
"""Print an error message and exit the script.
742+
743+
Args:
744+
e (Exception): The exception object caught.
745+
exit_code (int): The exit code to use when exiting the script.
746+
"""
747+
error_message = str(e).strip("'\"")
748+
perror(f"Error: {error_message}")
749+
sys.exit(exit_code)
750+
751+
752+
def run_command_with_error_handling(args, qmctl, parser):
753+
"""Execute the subcommand with centralized error handling.
754+
755+
Args:
756+
args: The parsed command-line arguments.
757+
qmctl: An instance of the QMCTL class.
758+
parser: The argparse parser object, used for printing usage.
759+
"""
760+
try:
761+
if not args.subcommand:
762+
parser.print_usage()
763+
perror("Error: requires a subcommand")
764+
sys.exit(errno.EINVAL)
765+
args.func(args, qmctl)
766+
except IndexError as e:
767+
handle_error(e, errno.EINVAL)
768+
except KeyError as e:
769+
handle_error(e, 1)
770+
except NotImplementedError as e:
771+
handle_error(e, errno.ENOTSUP)
772+
except subprocess.CalledProcessError as e:
773+
handle_error(e, e.returncode)
774+
except KeyboardInterrupt:
775+
sys.exit(0)
776+
except ConnectionError as e:
777+
handle_error(e, errno.EINVAL)
778+
except ValueError as e:
779+
handle_error(e, errno.EINVAL)
780+
except IOError as e:
781+
handle_error(e, errno.EIO)
782+
783+
784+
def create_argument_parser(description):
785+
"""Create and configure the argument parser for the CLI.
786+
787+
Args:
788+
description (str): The description text for the CLI tool.
789+
790+
Returns:
791+
An initialized ArgumentParser for the CLI.
792+
"""
793+
parser = ArgumentParserWithDefaults(
794+
prog="qmctl",
795+
description=description,
796+
formatter_class=argparse.RawTextHelpFormatter,
797+
)
798+
configure_arguments(parser)
799+
return parser
800+
801+
802+
def configure_arguments(parser):
803+
"""Configure the base command-line arguments for the parser.
804+
805+
Args:
806+
parser (argparse.ArgumentParser): The main argument parser.
807+
"""
808+
verbosity_group = parser.add_mutually_exclusive_group()
809+
verbosity_group.add_argument(
810+
"-v", "--verbose", action="store_true", help="Enable verbose output."
811+
)
812+
813+
814+
def parse_arguments(parser):
815+
"""Parse command line arguments.
816+
817+
Args:
818+
parser (argparse.ArgumentParser): The argument parser.
819+
820+
Returns:
821+
The parsed arguments object.
822+
"""
823+
return parser.parse_args()
824+
825+
826+
def post_parse_setup(args):
827+
"""Perform additional setup after parsing arguments.
828+
829+
Args:
830+
args: The parsed command-line arguments.
831+
"""
832+
pass
833+
834+
835+
def init_cli():
836+
"""Initialize the QMCTL CLI, parse arguments, and set up the environment.
837+
838+
Returns:
839+
A tuple containing the configured parser and the parsed arguments.
840+
"""
841+
description = get_description()
842+
parser = create_argument_parser(description)
843+
configure_subcommands(parser)
844+
args = parse_arguments(parser)
845+
post_parse_setup(args)
846+
return parser, args
847+
848+
849+
def get_description():
850+
"""Return the description of the QMCTL tool.
851+
852+
Returns:
853+
A string containing the tool's description.
854+
"""
855+
return "QmCTL: Command Line Interface for QMCTL"
856+
857+
858+
def configure_subcommands(parser):
859+
"""Add subcommand parsers to the main argument parser.
860+
861+
Args:
862+
parser (argparse.ArgumentParser): The main argument parser.
863+
"""
864+
subparsers = parser.add_subparsers(dest="subcommand")
865+
subparsers.required = False
866+
init_show_subcommand(subparsers)
867+
init_exec_subcommand(subparsers)
868+
init_execin_subcommand(subparsers)
869+
init_cp_subcommand(subparsers)
870+
871+
872+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
873+
# Start of the Subcommand initialization functions
874+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
875+
876+
877+
def init_show_subcommand(subparsers):
878+
"""Initialize the 'show' subcommand for displaying container info.
879+
880+
Args:
881+
subparsers: The subparser object from the main parser.
882+
"""
883+
name = "show"
884+
help_text = "Show information about the QM container"
885+
default_func = handle_show_command
886+
args_config = [
887+
{
888+
'name': ['show_command_topic'],
889+
'nargs': '?',
890+
'default': 'container',
891+
'choices': [
892+
"container",
893+
"unix-domain-sockets",
894+
"shared-memory",
895+
"resources",
896+
"available-devices",
897+
"namespaces",
898+
"all",
899+
],
900+
'help': "What to show"
901+
},
902+
{
903+
'name': ['--json'],
904+
'action': 'store_true',
905+
'help': "Output as JSON"
906+
}
907+
]
908+
SubcommandInitializer(
909+
subparsers, name, help_text, default_func, args_config
910+
).initialize()
911+
912+
913+
def init_exec_subcommand(subparsers):
914+
"""Initialize the 'exec' subcommand for running commands.
915+
916+
Args:
917+
subparsers: The subparser object from the main parser.
918+
"""
919+
name = "exec"
920+
help_text = "Run command inside QM container"
921+
default_func = handle_exec_command
922+
args_config = [
923+
{
924+
'name': ['cmd'],
925+
'nargs': argparse.REMAINDER,
926+
'help': "Command to execute"
927+
},
928+
{
929+
'name': ['--json'],
930+
'action': 'store_true',
931+
'help': "Output as JSON"
932+
}
933+
]
934+
SubcommandInitializer(
935+
subparsers, name, help_text, default_func, args_config
936+
).initialize()
937+
938+
939+
def init_execin_subcommand(subparsers):
940+
"""Initialize the 'execin' subcommand for nested execution.
941+
942+
Args:
943+
subparsers: The subparser object from the main parser.
944+
"""
945+
name = "execin"
946+
help_text = "Run command inside QM container in a nested container"
947+
default_func = handle_execin_command
948+
args_config = [
949+
{
950+
'name': ['cmd'],
951+
'nargs': argparse.REMAINDER,
952+
'help': "Command to execute in the nested container"
953+
},
954+
{
955+
'name': ['--json'],
956+
'action': 'store_true',
957+
'help': "Output as JSON"
958+
}
959+
]
960+
SubcommandInitializer(
961+
subparsers, name, help_text, default_func, args_config
962+
).initialize()
963+
964+
965+
def init_cp_subcommand(subparsers):
966+
"""Initialize the 'cp' subcommand for copying files.
967+
968+
Args:
969+
subparsers: The subparser object from the main parser.
970+
"""
971+
name = "cp"
972+
help_text = "Copy files between host and QM container"
973+
default_func = handle_cp_command
974+
args_config = [
975+
{
976+
'name': ['paths'],
977+
'nargs': 2,
978+
'help': ("Source and destination paths (e.g., "
979+
"/path/to/file /path/in/container)")
980+
},
981+
{
982+
'name': ['--json'],
983+
'action': 'store_true',
984+
'help': "Output as JSON"
985+
}
986+
]
987+
SubcommandInitializer(
988+
subparsers, name, help_text, default_func, args_config
989+
).initialize()
990+
991+
992+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
993+
# End of the Subcommand initialization functions
994+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
995+
996+
997+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
998+
# Start of the handle command functions
999+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1000+
1001+
1002+
def handle_show_command(args, qmctl):
1003+
"""Handle the logic for the 'show' subcommand.
1004+
1005+
Args:
1006+
args: The parsed command-line arguments.
1007+
qmctl: An instance of the QMCTL class.
1008+
"""
1009+
all_show_commands = {
1010+
"all": qmctl.show_all,
1011+
"container": qmctl.show_container,
1012+
"unix-domain-sockets": qmctl.show_unix_sockets,
1013+
"shared-memory": qmctl.show_shared_memory,
1014+
"resources": qmctl.show_resources,
1015+
"available-devices": qmctl.show_available_devices,
1016+
"namespaces": qmctl.show_namespaces,
1017+
}
1018+
1019+
command_to_execute = all_show_commands.get(
1020+
args.show_command_topic,
1021+
qmctl.show_container if args.show_command_topic is None else None)
1022+
1023+
if command_to_execute:
1024+
command_to_execute(output_json=args.json, pretty=True)
1025+
else:
1026+
print(f"Error: Unknown show command '{args.show_command_topic}'.")
1027+
1028+
1029+
def handle_exec_command(args, qmctl):
1030+
"""Handle the logic for the 'exec' subcommand.
1031+
1032+
Args:
1033+
args: The parsed command-line arguments.
1034+
qmctl: An instance of the QMCTL class.
1035+
"""
1036+
qmctl.exec_in_container(
1037+
command=args.cmd, output_json=args.json, pretty=True
1038+
)
1039+
1040+
1041+
def handle_execin_command(args, qmctl):
1042+
"""Handle the logic for the 'execin' subcommand.
1043+
1044+
Args:
1045+
args: The parsed command-line arguments.
1046+
qmctl: An instance of the QMCTL class.
1047+
"""
1048+
qmctl.execin_in_container(
1049+
command=args.cmd, output_json=args.json, pretty=True
1050+
)
1051+
1052+
1053+
def handle_cp_command(args, qmctl):
1054+
"""Handle the logic for the 'cp' subcommand.
1055+
1056+
Args:
1057+
args: The parsed command-line arguments.
1058+
qmctl: An instance of the QMCTL class.
1059+
"""
1060+
qmctl.copy_in_container(
1061+
paths=args.paths, output_json=args.json, pretty=True
1062+
)
1063+
1064+
1065+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1066+
# End of the handle command functions
1067+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1068+
1069+
1070+
def main():
1071+
"""Define the main execution flow of the script."""
1072+
parser, args = init_cli()
1073+
qmctl = QMCTL(verbose=args.verbose)
1074+
try:
1075+
import argcomplete
1076+
argcomplete.autocomplete(parser)
1077+
except Exception:
1078+
pass
1079+
1080+
run_command_with_error_handling(args, qmctl, parser)
1081+
1082+
1083+
if __name__ == "__main__":
1084+
main()

0 commit comments

Comments
 (0)