From 93b736a2249272b92783b59495c15f6eb4f3df96 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Jun 2015 15:26:33 -0700 Subject: [PATCH 1/4] Logs and hides tracebacks --- letsencrypt/cli.py | 43 +++++++++++++++++++++++++++-------- letsencrypt/tests/cli_test.py | 19 +++++++++++++++- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 118eb6e39..4f645c9b8 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -98,8 +98,8 @@ def _find_domains(args, installer): domains = args.domains if not domains: - sys.exit("Please specify --domains, or --installer that will " - "help in domain names autodiscovery") + raise errors.Error("Please specify --domains, or --installer that " + "will help in domain names autodiscovery") return domains @@ -116,7 +116,8 @@ def _init_acme(config, acc, authenticator, installer): acme.register() except errors.LetsEncryptClientError as error: logger.debug(error) - sys.exit("Unable to register an account with ACME server") + raise errors.Error("Unable to register an account with ACME " + "server") return acme @@ -465,6 +466,9 @@ def create_parser(plugins, args): "testing", description="The following flags are meant for " "testing purposes only! Do NOT change them, unless you " "really know what you're doing!") + helpful.add( + "testing", "--debug", action="store_true", + help="Show tracebacks if the program exits abnormally") helpful.add( "testing", "--no-verify-ssl", action="store_true", help=config_help("no_verify_ssl"), @@ -614,13 +618,8 @@ def _setup_logging(args): logger.info("Saving debug log to %s", log_file_name) -def main(cli_args=sys.argv[1:]): - """Command line argument parsing and main script execution.""" - # note: arg parser internally handles --help (and exits afterwards) - plugins = plugins_disco.PluginsRegistry.find_all() - args = create_parser(plugins, cli_args).parse_args(cli_args) - config = configuration.NamespaceConfig(args) - +def main2(cli_args, args, config, plugins): + """Continued main script execution.""" # Displayer if args.text_mode: displayer = display_util.FileDisplay(sys.stdout) @@ -659,5 +658,29 @@ def main(cli_args=sys.argv[1:]): return args.func(args, config, plugins) +def main(cli_args=sys.argv[1:]): + """Command line argument parsing and main script execution.""" + # note: arg parser internally handles --help (and exits afterwards) + plugins = plugins_disco.PluginsRegistry.find_all() + args = create_parser(plugins, cli_args).parse_args(cli_args) + config = configuration.NamespaceConfig(args) + + def handle_exception_common(): + """Logs the exception and reraises it if in debug mode.""" + logger.debug("Exiting abnormally", exc_info=True) + if args.debug: + raise + + try: + return main2(cli_args, args, config, plugins) + except errors.Error as error: + handle_exception_common() + return error + except: # pylint: disable=bare-except + handle_exception_common() + # Ensures a new line is printed + return "" + + if __name__ == "__main__": sys.exit(main()) # pragma: no cover diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 1e8e84f6d..5b3393996 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -20,13 +20,16 @@ class CLITest(unittest.TestCase): def tearDown(self): shutil.rmtree(self.tmp_dir) - def _call(self, args): + def _call(self, args, client_mock_attrs=None): from letsencrypt import cli args = ['--text', '--config-dir', self.config_dir, '--work-dir', self.work_dir, '--logs-dir', self.logs_dir] + args with mock.patch('letsencrypt.cli.sys.stdout') as stdout: with mock.patch('letsencrypt.cli.sys.stderr') as stderr: with mock.patch('letsencrypt.cli.client') as client: + if client_mock_attrs: + # pylint: disable=star-args + client.configure_mock(**client_mock_attrs) ret = cli.main(args) return ret, stdout, stderr, client @@ -55,6 +58,20 @@ class CLITest(unittest.TestCase): for r in xrange(len(flags)))): self._call(['plugins',] + list(args)) + def test_exceptions(self): + from letsencrypt import errors + cmd_arg = ['config_changes'] + error = [errors.Error('problem')] + attrs = {'view_config_changes.side_effect' : error} + with self.assertRaises(errors.Error): + self._call(['--debug'] + cmd_arg, attrs) + self._call(cmd_arg, attrs) + + attrs['view_config_changes.side_effect'] = [KeyboardInterrupt] + with self.assertRaises(KeyboardInterrupt): + self._call(['--debug'] + cmd_arg, attrs) + self._call(cmd_arg, attrs) + if __name__ == '__main__': unittest.main() # pragma: no cover From d6246ae309132f6f14ca1548ea5e40bed2f1ad8a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Jun 2015 16:09:46 -0700 Subject: [PATCH 2/4] Special cased KeyboardInterrupt --- letsencrypt/cli.py | 6 +++++- letsencrypt/tests/cli_test.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 4f645c9b8..dc93c838e 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -676,10 +676,14 @@ def main(cli_args=sys.argv[1:]): except errors.Error as error: handle_exception_common() return error - except: # pylint: disable=bare-except + except KeyboardInterrupt: handle_exception_common() # Ensures a new line is printed return "" + except: # pylint: disable=bare-except + handle_exception_common() + return ("\nAn unexpected error occured. Please see the logfiles in {0} " + "for more details.".format(args.logs_dir)) if __name__ == "__main__": diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 5b3393996..9c8363205 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -72,6 +72,10 @@ class CLITest(unittest.TestCase): self._call(['--debug'] + cmd_arg, attrs) self._call(cmd_arg, attrs) + attrs['view_config_changes.side_effect'] = [ValueError] + with self.assertRaises(ValueError): + self._call(['--debug'] + cmd_arg, attrs) + self._call(cmd_arg, attrs) if __name__ == '__main__': unittest.main() # pragma: no cover From 554139cb1a52c1c2f7de5437383e425ecad2443a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Jun 2015 16:14:21 -0700 Subject: [PATCH 3/4] Reformatted error message --- letsencrypt/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index dc93c838e..c6df1052c 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -682,7 +682,7 @@ def main(cli_args=sys.argv[1:]): return "" except: # pylint: disable=bare-except handle_exception_common() - return ("\nAn unexpected error occured. Please see the logfiles in {0} " + return ("An unexpected error occured. Please see the logfiles in {0} " "for more details.".format(args.logs_dir)) From 95a14324765b4439869c81f4dc7ba1a35124defe Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Jun 2015 16:40:59 -0700 Subject: [PATCH 4/4] Come on Python 2.6... --- letsencrypt/tests/cli_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 9c8363205..6b8f77c62 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -63,18 +63,18 @@ class CLITest(unittest.TestCase): cmd_arg = ['config_changes'] error = [errors.Error('problem')] attrs = {'view_config_changes.side_effect' : error} - with self.assertRaises(errors.Error): - self._call(['--debug'] + cmd_arg, attrs) + self.assertRaises( + errors.Error, self._call, ['--debug'] + cmd_arg, attrs) self._call(cmd_arg, attrs) attrs['view_config_changes.side_effect'] = [KeyboardInterrupt] - with self.assertRaises(KeyboardInterrupt): - self._call(['--debug'] + cmd_arg, attrs) + self.assertRaises( + KeyboardInterrupt, self._call, ['--debug'] + cmd_arg, attrs) self._call(cmd_arg, attrs) attrs['view_config_changes.side_effect'] = [ValueError] - with self.assertRaises(ValueError): - self._call(['--debug'] + cmd_arg, attrs) + self.assertRaises( + ValueError, self._call, ['--debug'] + cmd_arg, attrs) self._call(cmd_arg, attrs) if __name__ == '__main__':