Right now we catch and report properly only those exceptions that are raised by the Application.run() method: basically, we work under the assumption that nothing before that point can possibly fail. That's of course unrealistic: even now, creating an Inventory or Projects object can raise an exception, in which case we simply display a stack trace instead of a reasonable error message. This commit introduces a CommandLine class which takes over command line handling from the Application class, and moves all exception handling to the main() method so that, short of something going horribly wrong while parsing the command line, we will always manage to hide stack traces from the user. Signed-off-by: Andrea Bolognani <abologna@xxxxxxxxxx> --- guests/lcitool | 56 ++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/guests/lcitool b/guests/lcitool index ab3b95f..ff58829 100755 --- a/guests/lcitool +++ b/guests/lcitool @@ -390,15 +390,9 @@ class Projects: return self._packages[project] -class Application: +class CommandLine: def __init__(self): - self._config = Config() - self._inventory = Inventory() - self._projects = Projects() - - self._native_arch = Util.get_native_arch() - self._parser = argparse.ArgumentParser( conflict_handler="resolve", description="libvirt CI guest management tool", @@ -444,14 +438,14 @@ class Application: installparser = subparsers.add_parser( "install", help="perform unattended host installation") - installparser.set_defaults(func=self._action_install) + installparser.set_defaults(action="install") add_hosts_arg(installparser) add_wait_arg(installparser) updateparser = subparsers.add_parser( "update", help="prepare hosts and keep them updated") - updateparser.set_defaults(func=self._action_update) + updateparser.set_defaults(action="update") add_hosts_arg(updateparser) add_projects_arg(updateparser) @@ -459,7 +453,7 @@ class Application: buildparser = subparsers.add_parser( "build", help="build projects on hosts") - buildparser.set_defaults(func=self._action_build) + buildparser.set_defaults(action="build") add_hosts_arg(buildparser) add_projects_arg(buildparser) @@ -467,20 +461,33 @@ class Application: hostsparser = subparsers.add_parser( "hosts", help="list all known hosts") - hostsparser.set_defaults(func=self._action_hosts) + hostsparser.set_defaults(action="hosts") projectsparser = subparsers.add_parser( "projects", help="list all known projects") - projectsparser.set_defaults(func=self._action_projects) + projectsparser.set_defaults(action="projects") dockerfileparser = subparsers.add_parser( "dockerfile", help="generate Dockerfile (doesn't access the host)") - dockerfileparser.set_defaults(func=self._action_dockerfile) + dockerfileparser.set_defaults(action="dockerfile") add_hosts_arg(dockerfileparser) add_projects_arg(dockerfileparser) add_cross_arch_arg(dockerfileparser) + def parse(self): + return self._parser.parse_args() + + +class Application: + + def __init__(self): + self._config = Config() + self._inventory = Inventory() + self._projects = Projects() + + self._native_arch = Util.get_native_arch() + def _execute_playbook(self, playbook, hosts, projects, git_revision): base = Util.get_base() @@ -1011,17 +1018,18 @@ class Application: varmap = self._dockerfile_build_varmap(facts, mappings, pip_mappings, projects, cross_arch) self._dockerfile_format(facts, cross_arch, varmap) - def run(self): - args = self._parser.parse_args() - if args.debug: - args.func(args) - else: - try: - args.func(args) - except Exception as err: - sys.stderr.write("{}: {}\n".format(sys.argv[0], err)) - sys.exit(1) + def run(self, args): + getattr(self, "_action_" + args.action)(args) if __name__ == "__main__": - Application().run() + args = CommandLine().parse() + + if args.debug: + Application().run(args) + else: + try: + Application().run(args) + except Exception as err: + sys.stderr.write("{}: {}\n".format(sys.argv[0], err)) + sys.exit(1) -- 2.25.4