From 3457a01da2942c5436bca4dbfdf7789562ad40fe Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Sat, 6 Jun 2015 15:45:17 -0700 Subject: [PATCH 01/14] WIP: Cleaning up the command line help --- letsencrypt/cli.py | 48 ++++++++++++++++++++--------------- letsencrypt/constants.py | 2 +- letsencrypt/plugins/common.py | 2 ++ 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 4b0e271f7..2f3eabaec 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -135,7 +135,7 @@ def auth(args, config, plugins): def install(args, config, plugins): - """Install (no auth).""" + """Install cert in server software (no auth).""" # XXX: Update for renewer/RenewableCert acc = _account_init(args, config) if acc is None: @@ -224,9 +224,12 @@ def flag_default(name): """Default value for CLI flag.""" return constants.CLI_DEFAULTS[name] -def config_help(name): +def config_help(name, hidden=False): """Help message for `.IConfig` attribute.""" - return interfaces.IConfig[name].__doc__ + if hidden: + return argparse.SUPPRESS + else: + return interfaces.IConfig[name].__doc__ def create_parser(plugins): @@ -238,17 +241,19 @@ def create_parser(plugins): default_config_files=flag_default("config_files")) add = parser.add_argument + auto_group = parser.add_argument_group( + "auto", description="Arguments for automating execution") # --help is automatically provided by argparse - add("--version", action="version", version="%(prog)s {0}".format( - letsencrypt.__version__)) + auto_group.add_argument("--version", action="version", + version="%(prog)s {0}".format(letsencrypt.__version__)) add("-v", "--verbose", dest="verbose_count", action="count", default=flag_default("verbose_count"), help="This flag can be used " "multiple times to incrementally increase the verbosity of output, " "e.g. -vvv.") - add("--no-confirm", dest="no_confirm", action="store_true", + auto_group.add_argument("--no-confirm", dest="no_confirm", action="store_true", help="Turn off confirmation screens, currently used for --revoke") - add("-e", "--agree-tos", dest="tos", action="store_true", - help="Skip the end user license agreement screen.") + add("--agree-eula", "-e", dest="tos", action="store_true", + help="Agree to the Let's Encrypt Subscriber Agreement") add("-t", "--text", dest="text_mode", action="store_true", help="Use the text output instead of the curses UI.") @@ -258,11 +263,11 @@ def create_parser(plugins): "really know what you're doing!") testing_group.add_argument( "--no-verify-ssl", action="store_true", - help=config_help("no_verify_ssl"), + help=config_help("no_verify_ssl",hidden=True), default=flag_default("no_verify_ssl")) # TODO: apache and nginx plugins do NOT respect it testing_group.add_argument( - "--dvsni-port", type=int, help=config_help("dvsni_port"), + "--dvsni-port", type=int, help=config_help("dvsni_port",hidden=True), default=flag_default("dvsni_port")) subparsers = parser.add_subparsers(metavar="SUBCOMMAND") @@ -301,8 +306,8 @@ def create_parser(plugins): add("-d", "--domains", metavar="DOMAIN", action="append") add("-s", "--server", default=flag_default("server"), help=config_help("server")) - add("-k", "--authkey", type=read_file, - help="Path to the authorized key file") + add("-k", "--accountkey", type=read_file, + help="Path to the account key file") add("-m", "--email", help=config_help("email")) add("-B", "--rsa-key-size", type=int, metavar="N", default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) @@ -336,26 +341,27 @@ def create_parser(plugins): def _paths_parser(parser): add = parser.add_argument + hidden = True add("--config-dir", default=flag_default("config_dir"), - help=config_help("config_dir")) + help=config_help("config_dir", hidden)) add("--work-dir", default=flag_default("work_dir"), - help=config_help("work_dir")) + help=config_help("work_dir", hidden)) add("--backup-dir", default=flag_default("backup_dir"), - help=config_help("backup_dir")) + help=config_help("backup_dir", hidden)) add("--key-dir", default=flag_default("key_dir"), - help=config_help("key_dir")) + help=config_help("key_dir", hidden)) add("--cert-dir", default=flag_default("certs_dir"), - help=config_help("cert_dir")) + help=config_help("cert_dir", hidden)) add("--le-vhost-ext", default="-le-ssl.conf", - help=config_help("le_vhost_ext")) + help=config_help("le_vhost_ext", hidden)) add("--cert-path", default=flag_default("cert_path"), - help=config_help("cert_path")) + help=config_help("cert_path", hidden)) add("--chain-path", default=flag_default("chain_path"), - help=config_help("chain_path")) + help=config_help("chain_path", hidden)) add("--renewer-config-file", default=flag_default("renewer_config_file"), - help=config_help("renewer_config_file")) + help=config_help("renewer_config_file", hidden)) return parser diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index dacbe9040..739933bfd 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -23,7 +23,7 @@ CLI_DEFAULTS = dict( chain_path="/etc/letsencrypt/certs/chain-letsencrypt.pem", renewer_config_file="/etc/letsencrypt/renewer.conf", no_verify_ssl=False, - dvsni_port=challenges.DVSNI.PORT, + dvsni_port=443, ) """Defaults for CLI flags and `.IConfig` attributes.""" diff --git a/letsencrypt/plugins/common.py b/letsencrypt/plugins/common.py index 32bee2b49..241acbcf1 100644 --- a/letsencrypt/plugins/common.py +++ b/letsencrypt/plugins/common.py @@ -1,5 +1,6 @@ """Plugin common functions.""" import zope.interface +import argparse from acme.jose import util as jose_util @@ -55,6 +56,7 @@ class Plugin(object): # dummy function, doesn't check if dest.startswith(self.dest_namespace) def add(arg_name_no_prefix, *args, **kwargs): # pylint: disable=missing-docstring + kwargs["help"] = argparse.SUPPRESS return parser.add_argument( "--{0}{1}".format(option_namespace(name), arg_name_no_prefix), *args, **kwargs) From 1fa5a64abdf3006374a65bab2ad07de29d16ac7f Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Mon, 8 Jun 2015 01:38:39 -0700 Subject: [PATCH 02/14] Draft (somewhat buggy) implementation of help topics --- letsencrypt/cli.py | 238 ++++++++++++++++++++++++++++++++------------- 1 file changed, 171 insertions(+), 67 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 2f3eabaec..8e5c48a54 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -5,6 +5,7 @@ import atexit import logging import os import sys +import types import configargparse import zope.component @@ -231,46 +232,154 @@ def config_help(name, hidden=False): else: return interfaces.IConfig[name].__doc__ +class SilentParser: + """An a mini parser wrapper that doesn't print help for its + arguments... this one is just needed to the use of callbacks to define + arguments within plugins""" + def __init__(self, parser): + self.parser = parser + def add_argument(self, *args, **kwargs): + kwargs["help"] = argparse.SUPPRESS + self.parser.add_argument(*args, **kwargs) -def create_parser(plugins): +HELP_TOPICS = ["all","security","paths","automation","testing"] +class HelpfulArgumentParser: + """This class wraps argparse, adding the ability to make --help less + verbose, and request help on specific subcategories at a time, eg + 'letsencrypt --help security' for security options.""" + + def __init__(self, args, plugins): + self.args = args + plugin_names = [name for name, p in plugins.iteritems()] + self.help_topics = HELP_TOPICS + plugin_names + self.parser = configargparse.ArgParser( + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + args_for_setting_config_path=["-c", "--config"], + default_config_files=flag_default("config_files")) + self.silent_parser = SilentParser(self.parser) + + h1 = self.prescan_for_flag("-h", self.help_topics) + h2 = self.prescan_for_flag("--h", self.help_topics) + assert max(True,"a") == "a", "Gravity changed direction" + help_arg = max(h1, h2) + self.visible_topics = self.determine_help_topics(help_arg) + print self.visible_topics + self.groups = {} # elements are added by .add_group() + self.add_plugin_args(plugins) + + def prescan_for_flag(self, flag, possible_arguments): + """check for a flag, which accepts a fixed set of possible arguments, in + the command line; we will use this information to configure argparse's + help correctly. Return the flag's argument, if it has one that matches + the sequence @possible_arguments; otherwise return whether the flag is + present""" + if flag not in self.args: + return False + pos = self.args.index(flag) + try: + nxt = self.args[pos + 1] + if nxt in possible_arguments: + return nxt + except: + pass + return True + + def add(self, topic, *args, **kwargs): + """Add a new command line argument. @topic is required, to indicate + which part of the help will document it, but can be None for `always + documented'.""" + + if topic and self.visible_topics[topic]: + g = self.groups[topic] + g.add_argument(*args, **kwargs) + else: + kwargs["help"] = argparse.SUPPRESS + self.parser.add_argument(*args, **kwargs) + + def add_group(self, topic, **kwargs): + """This has to be called once for every topic; but we leave those calls + next to the argument definitions for clarity. Return something + arguments can be added to if necessary, either the parser or an argument + group.""" + if self.visible_topics[topic]: + #print "Adding visible group " + topic + g = self.parser.add_argument_group(topic, **kwargs) + self.groups[topic] = g + #def silencing_shim(self2, *args,**kwargs): + # kwargs["help"] = argparse.SUPPRESS + # self2._real_add_argument(*args, **kwargs) + #g._real_add_argument = g.add_argument + #g.add_argument = types.MethodType(silencing_shim, g) + return g + else: + #print "Invisible group " + topic + # The plugins is going to try to add non-silent arguments; we have + # to stop that... + return self.silent_parser + + def add_plugin_args(self, plugins): + # TODO: plugin_parser should be called for every detected plugin + for name, plugin_ep in plugins.iteritems(): + parser_or_group = self.add_group(name, description=plugin_ep.description) + #print parser_or_group + plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) + + def determine_help_topics(self, chosen_topic): + """The user may have requested help on a topic, return a dict of which + topics to dislpay. @chosen_topic has prescan_for_flag's return type""" + + # topics maps each topic to whether it should be documented by + # argparse on the command line + if chosen_topic == "all": + return dict([(t,True) for t in self.help_topics]) + elif not chosen_topic: + return dict([(t,False) for t in self.help_topics]) + else: + return dict([(t,t == chosen_topic) for t in self.help_topics]) + + +def create_parser(plugins, args): """Create parser.""" - parser = configargparse.ArgParser( - description=__doc__, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - args_for_setting_config_path=["-c", "--config"], - default_config_files=flag_default("config_files")) - add = parser.add_argument + helpful = HelpfulArgumentParser(args, plugins) - auto_group = parser.add_argument_group( - "auto", description="Arguments for automating execution") - # --help is automatically provided by argparse - auto_group.add_argument("--version", action="version", - version="%(prog)s {0}".format(letsencrypt.__version__)) - add("-v", "--verbose", dest="verbose_count", action="count", + helpful.add( + None, "-v", "--verbose", dest="verbose_count", action="count", default=flag_default("verbose_count"), help="This flag can be used " "multiple times to incrementally increase the verbosity of output, " "e.g. -vvv.") - auto_group.add_argument("--no-confirm", dest="no_confirm", action="store_true", + # --help is automatically provided by argparse + + helpful.add_group( "automation", + description="Arguments for automating execution & other tweaks") + helpful.add( + "automation", "--version", action="version", + version="%(prog)s {0}".format(letsencrypt.__version__), + help="show program's version number and exit") + helpful.add( + "automation", "--no-confirm", dest="no_confirm", action="store_true", help="Turn off confirmation screens, currently used for --revoke") - add("--agree-eula", "-e", dest="tos", action="store_true", + helpful.add( + "automation", "--agree-eula", "-e", dest="tos", action="store_true", help="Agree to the Let's Encrypt Subscriber Agreement") - add("-t", "--text", dest="text_mode", action="store_true", + helpful.add( + None, "-t", "--text", dest="text_mode", action="store_true", help="Use the text output instead of the curses UI.") - testing_group = parser.add_argument_group( + helpful.add_group( "testing", description="The following flags are meant for " "testing purposes only! Do NOT change them, unless you " "really know what you're doing!") - testing_group.add_argument( - "--no-verify-ssl", action="store_true", + helpful.add( + "testing", "--no-verify-ssl", action="store_true", help=config_help("no_verify_ssl",hidden=True), default=flag_default("no_verify_ssl")) # TODO: apache and nginx plugins do NOT respect it - testing_group.add_argument( - "--dvsni-port", type=int, help=config_help("dvsni_port",hidden=True), - default=flag_default("dvsni_port")) + helpful.add( + "testing", "--dvsni-port", type=int, default=flag_default("dvsni_port"), + help=config_help("dvsni_port",hidden=True)) - subparsers = parser.add_subparsers(metavar="SUBCOMMAND") + subparsers = helpful.parser.add_subparsers(metavar="SUBCOMMAND") def add_subparser(name, func): # pylint: disable=missing-docstring subparser = subparsers.add_parser( name, help=func.__doc__.splitlines()[0], description=func.__doc__) @@ -294,25 +403,27 @@ def create_parser(plugins): "--installers", action="append_const", dest="ifaces", const=interfaces.IInstaller) - parser.add_argument("--configurator") - parser.add_argument("-a", "--authenticator") - parser.add_argument("-i", "--installer") + helpful.add(None, "--configurator") + helpful.add(None, "-a", "--authenticator") + helpful.add(None, "-i", "--installer") # positional arg shadows --domains, instead of appending, and # --domains is useful, because it can be stored in config #for subparser in parser_run, parser_auth, parser_install: # subparser.add_argument("domains", nargs="*", metavar="domain") - add("-d", "--domains", metavar="DOMAIN", action="append") - add("-s", "--server", default=flag_default("server"), - help=config_help("server")) - add("-k", "--accountkey", type=read_file, + helpful.add(None, "-d", "--domains", metavar="DOMAIN", action="append") + helpful.add(None, "-k", "--accountkey", type=read_file, help="Path to the account key file") - add("-m", "--email", help=config_help("email")) - add("-B", "--rsa-key-size", type=int, metavar="N", - default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) + helpful.add(None, "-m", "--email", help=config_help("email")) + + helpful.add_group( + "security", description="Security parameters & server settings") + helpful.add("security", "-B", "--rsa-key-size", type=int, metavar="N", + default=flag_default("rsa_key_size"), + help=config_help("rsa_key_size",True)) # TODO: resolve - assumes binary logic while client.py assumes ternary. - add("-r", "--redirect", action="store_true", + helpful.add("security", "-r", "--redirect", action="store_true", help="Automatically redirect all HTTP traffic to HTTPS for the newly " "authenticated vhost.") @@ -328,49 +439,42 @@ def create_parser(plugins): default=flag_default("rollback_checkpoints"), help="Revert configuration N number of checkpoints.") - _paths_parser(parser.add_argument_group("paths")) - - # TODO: plugin_parser should be called for every detected plugin - for name, plugin_ep in plugins.iteritems(): - plugin_ep.plugin_cls.inject_parser_options( - parser.add_argument_group( - name, description=plugin_ep.description), name) - - return parser + _paths_parser(helpful) -def _paths_parser(parser): - add = parser.add_argument - hidden = True - add("--config-dir", default=flag_default("config_dir"), - help=config_help("config_dir", hidden)) - add("--work-dir", default=flag_default("work_dir"), - help=config_help("work_dir", hidden)) - add("--backup-dir", default=flag_default("backup_dir"), - help=config_help("backup_dir", hidden)) - add("--key-dir", default=flag_default("key_dir"), - help=config_help("key_dir", hidden)) - add("--cert-dir", default=flag_default("certs_dir"), - help=config_help("cert_dir", hidden)) + return helpful.parser - add("--le-vhost-ext", default="-le-ssl.conf", - help=config_help("le_vhost_ext", hidden)) - add("--cert-path", default=flag_default("cert_path"), - help=config_help("cert_path", hidden)) - add("--chain-path", default=flag_default("chain_path"), - help=config_help("chain_path", hidden)) - add("--renewer-config-file", default=flag_default("renewer_config_file"), - help=config_help("renewer_config_file", hidden)) - - return parser +def _paths_parser(helpful): + add = helpful.add + helpful.add_group("paths", description="Arguments changing execution paths & servers") + add("paths", "--config-dir", default=flag_default("config_dir"), + help=config_help("config_dir")) + add("paths", "--work-dir", default=flag_default("work_dir"), + help=config_help("work_dir")) + add("paths", "--backup-dir", default=flag_default("backup_dir"), + help=config_help("backup_dir")) + add("paths", "--key-dir", default=flag_default("key_dir"), + help=config_help("key_dir")) + add("paths", "--cert-dir", default=flag_default("certs_dir"), + help=config_help("cert_dir")) + add("paths", "--le-vhost-ext", default="-le-ssl.conf", + help=config_help("le_vhost_ext")) + add("paths", "--cert-path", default=flag_default("cert_path"), + help=config_help("cert_path")) + add("paths", "--chain-path", default=flag_default("chain_path"), + help=config_help("chain_path")) + add("paths", "--renewer-config-file", default=flag_default("renewer_config_file"), + help=config_help("renewer_config_file")) + add("paths","-s", "--server", default=flag_default("server"), + help=config_help("server")) def main(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).parse_args(args) + args = create_parser(plugins,args).parse_args(args) config = configuration.NamespaceConfig(args) # Displayer From 2d65026d6dccf50c4abe988e070b6e69cebc1a69 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Mon, 8 Jun 2015 11:42:15 -0700 Subject: [PATCH 03/14] Help topics now working --- letsencrypt/cli.py | 8 ++++---- letsencrypt/plugins/common.py | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 8e5c48a54..65898c755 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -264,7 +264,7 @@ class HelpfulArgumentParser: assert max(True,"a") == "a", "Gravity changed direction" help_arg = max(h1, h2) self.visible_topics = self.determine_help_topics(help_arg) - print self.visible_topics + #print self.visible_topics self.groups = {} # elements are added by .add_group() self.add_plugin_args(plugins) @@ -372,12 +372,12 @@ def create_parser(plugins, args): "really know what you're doing!") helpful.add( "testing", "--no-verify-ssl", action="store_true", - help=config_help("no_verify_ssl",hidden=True), + help=config_help("no_verify_ssl"), default=flag_default("no_verify_ssl")) # TODO: apache and nginx plugins do NOT respect it helpful.add( "testing", "--dvsni-port", type=int, default=flag_default("dvsni_port"), - help=config_help("dvsni_port",hidden=True)) + help=config_help("dvsni_port")) subparsers = helpful.parser.add_subparsers(metavar="SUBCOMMAND") def add_subparser(name, func): # pylint: disable=missing-docstring @@ -421,7 +421,7 @@ def create_parser(plugins, args): "security", description="Security parameters & server settings") helpful.add("security", "-B", "--rsa-key-size", type=int, metavar="N", default=flag_default("rsa_key_size"), - help=config_help("rsa_key_size",True)) + help=config_help("rsa_key_size")) # TODO: resolve - assumes binary logic while client.py assumes ternary. helpful.add("security", "-r", "--redirect", action="store_true", help="Automatically redirect all HTTP traffic to HTTPS for the newly " diff --git a/letsencrypt/plugins/common.py b/letsencrypt/plugins/common.py index 241acbcf1..32bee2b49 100644 --- a/letsencrypt/plugins/common.py +++ b/letsencrypt/plugins/common.py @@ -1,6 +1,5 @@ """Plugin common functions.""" import zope.interface -import argparse from acme.jose import util as jose_util @@ -56,7 +55,6 @@ class Plugin(object): # dummy function, doesn't check if dest.startswith(self.dest_namespace) def add(arg_name_no_prefix, *args, **kwargs): # pylint: disable=missing-docstring - kwargs["help"] = argparse.SUPPRESS return parser.add_argument( "--{0}{1}".format(option_namespace(name), arg_name_no_prefix), *args, **kwargs) From c70bce8b7ade2bb06c3f0333307346bf0473775d Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Mon, 8 Jun 2015 16:51:15 -0700 Subject: [PATCH 04/14] Some cleaning up --- letsencrypt/cli.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 65898c755..d4e416881 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -5,7 +5,6 @@ import atexit import logging import os import sys -import types import configargparse import zope.component @@ -306,16 +305,9 @@ class HelpfulArgumentParser: #print "Adding visible group " + topic g = self.parser.add_argument_group(topic, **kwargs) self.groups[topic] = g - #def silencing_shim(self2, *args,**kwargs): - # kwargs["help"] = argparse.SUPPRESS - # self2._real_add_argument(*args, **kwargs) - #g._real_add_argument = g.add_argument - #g.add_argument = types.MethodType(silencing_shim, g) return g else: #print "Invisible group " + topic - # The plugins is going to try to add non-silent arguments; we have - # to stop that... return self.silent_parser def add_plugin_args(self, plugins): From 8dc9cc67d96691cbbaad43dfeef9b7bab5a1f116 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Tue, 16 Jun 2015 12:46:37 -0700 Subject: [PATCH 05/14] Satiate the pylint daemons --- letsencrypt/cli.py | 54 ++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index d4e416881..e1c563a48 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -231,25 +231,26 @@ def config_help(name, hidden=False): else: return interfaces.IConfig[name].__doc__ -class SilentParser: +class SilentParser(object): """An a mini parser wrapper that doesn't print help for its arguments... this one is just needed to the use of callbacks to define arguments within plugins""" def __init__(self, parser): self.parser = parser def add_argument(self, *args, **kwargs): + """Wrap, but silence help""" kwargs["help"] = argparse.SUPPRESS self.parser.add_argument(*args, **kwargs) -HELP_TOPICS = ["all","security","paths","automation","testing"] -class HelpfulArgumentParser: +HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] +class HelpfulArgumentParser(object): """This class wraps argparse, adding the ability to make --help less verbose, and request help on specific subcategories at a time, eg 'letsencrypt --help security' for security options.""" def __init__(self, args, plugins): self.args = args - plugin_names = [name for name, p in plugins.iteritems()] + plugin_names = [name for name, _p in plugins.iteritems()] self.help_topics = HELP_TOPICS + plugin_names self.parser = configargparse.ArgParser( description=__doc__, @@ -259,8 +260,8 @@ class HelpfulArgumentParser: self.silent_parser = SilentParser(self.parser) h1 = self.prescan_for_flag("-h", self.help_topics) - h2 = self.prescan_for_flag("--h", self.help_topics) - assert max(True,"a") == "a", "Gravity changed direction" + h2 = self.prescan_for_flag("--help", self.help_topics) + assert max(True, "a") == "a", "Gravity changed direction" help_arg = max(h1, h2) self.visible_topics = self.determine_help_topics(help_arg) #print self.visible_topics @@ -276,11 +277,11 @@ class HelpfulArgumentParser: if flag not in self.args: return False pos = self.args.index(flag) - try: + try: nxt = self.args[pos + 1] if nxt in possible_arguments: return nxt - except: + except IndexError: pass return True @@ -288,10 +289,10 @@ class HelpfulArgumentParser: """Add a new command line argument. @topic is required, to indicate which part of the help will document it, but can be None for `always documented'.""" - + if topic and self.visible_topics[topic]: - g = self.groups[topic] - g.add_argument(*args, **kwargs) + group = self.groups[topic] + group.add_argument(*args, **kwargs) else: kwargs["help"] = argparse.SUPPRESS self.parser.add_argument(*args, **kwargs) @@ -299,18 +300,20 @@ class HelpfulArgumentParser: def add_group(self, topic, **kwargs): """This has to be called once for every topic; but we leave those calls next to the argument definitions for clarity. Return something - arguments can be added to if necessary, either the parser or an argument + arguments can be added to if necessary, either the parser or an argument group.""" if self.visible_topics[topic]: - #print "Adding visible group " + topic - g = self.parser.add_argument_group(topic, **kwargs) - self.groups[topic] = g - return g + #print "Adding visible group " + topic + group = self.parser.add_argument_group(topic, **kwargs) + self.groups[topic] = group + return group else: - #print "Invisible group " + topic + #print "Invisible group " + topic return self.silent_parser def add_plugin_args(self, plugins): + """Let each of the plugins add its own command line arguments, which + may or may not be displayed as help topics.""" # TODO: plugin_parser should be called for every detected plugin for name, plugin_ep in plugins.iteritems(): parser_or_group = self.add_group(name, description=plugin_ep.description) @@ -324,11 +327,11 @@ class HelpfulArgumentParser: # topics maps each topic to whether it should be documented by # argparse on the command line if chosen_topic == "all": - return dict([(t,True) for t in self.help_topics]) + return dict([(t, True) for t in self.help_topics]) elif not chosen_topic: - return dict([(t,False) for t in self.help_topics]) + return dict([(t, False) for t in self.help_topics]) else: - return dict([(t,t == chosen_topic) for t in self.help_topics]) + return dict([(t, t == chosen_topic) for t in self.help_topics]) def create_parser(plugins, args): @@ -342,10 +345,10 @@ def create_parser(plugins, args): "e.g. -vvv.") # --help is automatically provided by argparse - helpful.add_group( "automation", + helpful.add_group("automation", description="Arguments for automating execution & other tweaks") helpful.add( - "automation", "--version", action="version", + "automation", "--version", action="version", version="%(prog)s {0}".format(letsencrypt.__version__), help="show program's version number and exit") helpful.add( @@ -363,7 +366,7 @@ def create_parser(plugins, args): "testing purposes only! Do NOT change them, unless you " "really know what you're doing!") helpful.add( - "testing", "--no-verify-ssl", action="store_true", + "testing", "--no-verify-ssl", action="store_true", help=config_help("no_verify_ssl"), default=flag_default("no_verify_ssl")) # TODO: apache and nginx plugins do NOT respect it @@ -458,15 +461,14 @@ def _paths_parser(helpful): help=config_help("chain_path")) add("paths", "--renewer-config-file", default=flag_default("renewer_config_file"), help=config_help("renewer_config_file")) - - add("paths","-s", "--server", default=flag_default("server"), + add("paths", "-s", "--server", default=flag_default("server"), help=config_help("server")) def main(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,args).parse_args(args) + args = create_parser(plugins, args).parse_args(args) config = configuration.NamespaceConfig(args) # Displayer From 88a03afe7bda341fcf8bed8a4f07d468687a3824 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Tue, 16 Jun 2015 12:47:07 -0700 Subject: [PATCH 06/14] Prevent pylint from complaining about some silly things --- .pylintrc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.pylintrc b/.pylintrc index 5302133ad..825699036 100644 --- a/.pylintrc +++ b/.pylintrc @@ -38,7 +38,7 @@ load-plugins=linter_plugin # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=fixme,locally-disabled,abstract-class-not-used +disable=fixme,locally-disabled,abstract-class-not-used,bad-continuation,too-few-public-methods # abstract-class-not-used cannot be disabled locally (at least in pylint 1.4.1) @@ -101,7 +101,7 @@ function-rgx=[a-z_][a-z0-9_]{2,40}$ function-name-hint=[a-z_][a-z0-9_]{2,40}$ # Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ +variable-rgx=[a-z_][a-z0-9_]{1,30}$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,30}$ @@ -228,7 +228,8 @@ max-module-lines=1250 indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 +# This does something silly/broken... +#indent-after-paren=4 [TYPECHECK] From 40871d4c29f46dd4a001a9e5f8cb24e11bba27be Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Tue, 16 Jun 2015 14:04:41 -0700 Subject: [PATCH 07/14] Fix merge error --- letsencrypt/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 4461d2c02..a48336fe7 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -362,7 +362,7 @@ def create_parser(plugins, args): help="Use the text output instead of the curses UI.") - testing_group = parser.add_argument_group( + helpful.add_group( "testing", description="The following flags are meant for " "testing purposes only! Do NOT change them, unless you " "really know what you're doing!") From 52e20769bb9383938b3bc84caeaec114d3aae388 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Tue, 16 Jun 2015 14:19:19 -0700 Subject: [PATCH 08/14] Revert workaround for #482 --- letsencrypt/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index ae783d6bc..47539615d 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -23,7 +23,7 @@ CLI_DEFAULTS = dict( chain_path="/etc/letsencrypt/certs/chain-letsencrypt.pem", renewer_config_file="/etc/letsencrypt/renewer.conf", no_verify_ssl=False, - dvsni_port=443, + dvsni_port=challenges.DVSNI.PORT, ) """Defaults for CLI flags and `.IConfig` attributes.""" From fa0988289212f752c00fb57679eedf03e029b0d2 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Mon, 22 Jun 2015 01:09:43 -0700 Subject: [PATCH 09/14] Change permission error message Commonly, this will be caused by a failure to sudo, so the previous text was not necessarily going to be helpful. --- letsencrypt/le_util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/letsencrypt/le_util.py b/letsencrypt/le_util.py index ba2427c79..6bf26ff7e 100644 --- a/letsencrypt/le_util.py +++ b/letsencrypt/le_util.py @@ -33,8 +33,7 @@ def make_or_verify_dir(directory, mode=0o755, uid=0): if exception.errno == errno.EEXIST: if not check_permissions(directory, mode, uid): raise errors.LetsEncryptClientError( - "%s exists, but does not have the proper " - "permissions or owner" % directory) + "%s exists, this client can't access it" % directory) else: raise From f408ac7296782789cb802863e5cad69804d67eb0 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Mon, 22 Jun 2015 09:37:57 -0700 Subject: [PATCH 10/14] Draft basic usage text --- letsencrypt/cli.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index a48336fe7..36055d909 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -28,6 +28,38 @@ from letsencrypt.display import ops as display_ops from letsencrypt.plugins import disco as plugins_disco +USAGE = """ + letsencrypt [SUBCOMMAND] [options] [domains] + +The Let's Encrypt agent can obtain and install HTTPS/TLS/SSL certificates. By +default, it will attempt to use a webserver both for obtaining and installing +the cert. Major SUBCOMMANDS are: + + (default) Obtain & install a cert in your current webserver + auth Authenticate & obtain cert, but do not install it + install Install a previously obtained cert in a server + revoke Revoke a previously obtained certificate + rollback Rollback server configuration changes made during install + config-changes Show changes made to server config during installation + +Choice of server for authentication/installation: + + --apache Use the Apache plugin for authentication & installation + --nginx Use the Nginx plugin for authentication & installation + --standalone Run a standalone HTTPS server (for authentication only) + OR: + --authenticator standalone --installer nginx + +More detailed help: + + -h, --help [topic] print this message, or detailed help on a topic; + the available topics are: + + all, apache, automation, nginx, paths, security, testing, or any of the + sucommands +""" + + def _account_init(args, config): le_util.make_or_verify_dir( From 02f3bb4f0532fbe63d1e397cf72d5dfa0e6a1d70 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Tue, 23 Jun 2015 14:13:52 -0700 Subject: [PATCH 11/14] Use a completely custom usage mesage for plain --help Keep argparse in place for --help , but try to make that match the customised short help as much as possible. --- letsencrypt/cli.py | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 36055d909..ee95a35c5 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -25,22 +25,30 @@ from letsencrypt import reporter from letsencrypt.display import util as display_util from letsencrypt.display import ops as display_ops - from letsencrypt.plugins import disco as plugins_disco -USAGE = """ +# Argparse's help formatting has a lot of unhelpful peculiarities, so we want +# to replace as much of it as we can... + +# This is the stub to include in help generated by argparse + +SHORT_USAGE = """ letsencrypt [SUBCOMMAND] [options] [domains] The Let's Encrypt agent can obtain and install HTTPS/TLS/SSL certificates. By default, it will attempt to use a webserver both for obtaining and installing -the cert. Major SUBCOMMANDS are: +the cert. """ - (default) Obtain & install a cert in your current webserver - auth Authenticate & obtain cert, but do not install it - install Install a previously obtained cert in a server - revoke Revoke a previously obtained certificate - rollback Rollback server configuration changes made during install - config-changes Show changes made to server config during installation +# This is the short help for letsencrypt --help, where we disable argparse +# altogether +USAGE = SHORT_USAGE + """Major SUBCOMMANDS are: + + (default) everything Obtain & install a cert in your current webserver + auth Authenticate & obtain cert, but do not install it + install Install a previously obtained cert in a server + revoke Revoke a previously obtained certificate + rollback Rollback server configuration changes made during install + config-changes Show changes made to server config during installation Choice of server for authentication/installation: @@ -142,7 +150,7 @@ def run(args, config, plugins): def auth(args, config, plugins): - """Obtain a certificate (no install).""" + """Authenticate & obtain cert, but do not install it""" # XXX: Update for renewer / RenewableCert acc = _account_init(args, config) if acc is None: @@ -167,7 +175,7 @@ def auth(args, config, plugins): def install(args, config, plugins): - """Install cert in server software (no auth).""" + """Install a previously obtained cert in a server""" # XXX: Update for renewer/RenewableCert acc = _account_init(args, config) if acc is None: @@ -184,7 +192,7 @@ def install(args, config, plugins): def revoke(args, unused_config, unused_plugins): - """Revoke.""" + """Revoke a previously obtained certificate""" if args.rev_cert is None and args.rev_key is None: return "At least one of --certificate or --key is required" @@ -196,12 +204,12 @@ def revoke(args, unused_config, unused_plugins): def rollback(args, config, plugins): - """Rollback.""" + """Rollback server configuration changes made during install""" client.rollback(args.installer, args.checkpoints, config, plugins) def config_changes(unused_args, config, unused_plugins): - """View config changes. + """Show changes made to server config during installation View checkpoints and associated configuration changes. @@ -210,7 +218,7 @@ def config_changes(unused_args, config, unused_plugins): def plugins_cmd(args, config, plugins): # TODO: Use IDiplay rathern than print - """List plugins.""" + """List server software plugins""" logging.debug("Expected interfaces: %s", args.ifaces) ifaces = [] if args.ifaces is None else args.ifaces @@ -285,16 +293,22 @@ class HelpfulArgumentParser(object): plugin_names = [name for name, _p in plugins.iteritems()] self.help_topics = HELP_TOPICS + plugin_names self.parser = configargparse.ArgParser( - description=__doc__, + usage=SHORT_USAGE, formatter_class=argparse.ArgumentDefaultsHelpFormatter, args_for_setting_config_path=["-c", "--config"], default_config_files=flag_default("config_files")) + + self.parser._add_config_file_help = False self.silent_parser = SilentParser(self.parser) h1 = self.prescan_for_flag("-h", self.help_topics) h2 = self.prescan_for_flag("--help", self.help_topics) assert max(True, "a") == "a", "Gravity changed direction" help_arg = max(h1, h2) + if help_arg == True: + # just --help with no topic; avoid argparse altogether + print USAGE + sys.exit(0) self.visible_topics = self.determine_help_topics(help_arg) #print self.visible_topics self.groups = {} # elements are added by .add_group() From 079eb93e533093d9500ab0cf90002ab1490fb34e Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Tue, 23 Jun 2015 15:10:42 -0700 Subject: [PATCH 12/14] Satisfy the lintmonster --- letsencrypt/cli.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index ee95a35c5..bdc287370 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -298,17 +298,18 @@ class HelpfulArgumentParser(object): args_for_setting_config_path=["-c", "--config"], default_config_files=flag_default("config_files")) - self.parser._add_config_file_help = False + # This is the only way to turn off overly verbose config flag documentation + self.parser._add_config_file_help = False # pylint: disable=protected-access self.silent_parser = SilentParser(self.parser) - h1 = self.prescan_for_flag("-h", self.help_topics) - h2 = self.prescan_for_flag("--help", self.help_topics) + help1 = self.prescan_for_flag("-h", self.help_topics) + help2 = self.prescan_for_flag("--help", self.help_topics) assert max(True, "a") == "a", "Gravity changed direction" - help_arg = max(h1, h2) + help_arg = max(help1, help2) if help_arg == True: - # just --help with no topic; avoid argparse altogether - print USAGE - sys.exit(0) + # just --help with no topic; avoid argparse altogether + print USAGE + sys.exit(0) self.visible_topics = self.determine_help_topics(help_arg) #print self.visible_topics self.groups = {} # elements are added by .add_group() From 7fe5b8233bee880a2b143135fd7632f797f94905 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Tue, 23 Jun 2015 17:38:02 -0700 Subject: [PATCH 13/14] Retry, with sanity. --- letsencrypt/le_util.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/letsencrypt/le_util.py b/letsencrypt/le_util.py index f8c457c37..0f8207b7a 100644 --- a/letsencrypt/le_util.py +++ b/letsencrypt/le_util.py @@ -32,14 +32,8 @@ def make_or_verify_dir(directory, mode=0o755, uid=0): except OSError as exception: if exception.errno == errno.EEXIST: if not check_permissions(directory, mode, uid): -<<<<<<< HEAD - raise errors.LetsEncryptClientError( - "%s exists, this client can't access it" % directory) -======= raise errors.Error( - "%s exists, but does not have the proper " - "permissions or owner" % directory) ->>>>>>> letsencrypt/master + "%s exists, this client can't access it" % directory) else: raise From 15258cc50a6f8694eab0710d4e5a6fad15b657c0 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 24 Jun 2015 04:53:11 +0000 Subject: [PATCH 14/14] Update references after repo rename. https://github.com/letsencrypt/letsencrypt/issues/505 --- CHANGES.rst | 2 +- Dockerfile | 2 +- LICENSE.txt | 2 +- README.rst | 8 ++++---- docs/contributing.rst | 2 +- docs/pkgs.rst | 2 +- docs/using.rst | 10 +++++----- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 741d9bc7c..3ed13041b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,7 +3,7 @@ ChangeLog Please note: the change log will only get updated after first release - for now please use the -`commit log `_. +`commit log `_. Release 0.1.0 (not released yet) diff --git a/Dockerfile b/Dockerfile index 78aa7a75b..479aa4e85 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# https://github.com/letsencrypt/lets-encrypt-preview/pull/431#issuecomment-103659297 +# https://github.com/letsencrypt/letsencrypt/pull/431#issuecomment-103659297 # it is more likely developers will already have ubuntu:trusty rather # than e.g. debian:jessie and image size differences are negligible FROM ubuntu:trusty diff --git a/LICENSE.txt b/LICENSE.txt index d3c19bbd1..5a9f6fa55 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Let's Encrypt Preview: +Let's Encrypt: Copyright (c) Internet Security Research Group Licensed Apache Version 2.0 diff --git a/README.rst b/README.rst index 784e06436..7c98999e8 100644 --- a/README.rst +++ b/README.rst @@ -45,16 +45,16 @@ server automatically!:: :target: https://travis-ci.org/letsencrypt/letsencrypt :alt: Travis CI status -.. |coverage| image:: https://coveralls.io/repos/letsencrypt/lets-encrypt-preview/badge.svg?branch=master - :target: https://coveralls.io/r/letsencrypt/lets-encrypt-preview +.. |coverage| image:: https://coveralls.io/repos/letsencrypt/letsencrypt/badge.svg?branch=master + :target: https://coveralls.io/r/letsencrypt/letsencrypt :alt: Coverage status .. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/ :target: https://readthedocs.org/projects/letsencrypt/ :alt: Documentation status -.. |container| image:: https://quay.io/repository/letsencrypt/lets-encrypt-preview/status - :target: https://quay.io/repository/letsencrypt/lets-encrypt-preview +.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status + :target: https://quay.io/repository/letsencrypt/letsencrypt :alt: Docker Repository on Quay.io .. _`installation instructions`: diff --git a/docs/contributing.rst b/docs/contributing.rst index 804cec95c..05a6875fe 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -97,7 +97,7 @@ Configurators may implement just one of those). There are also `~letsencrypt.interfaces.IDisplay` plugins, which implement bindings to alternative UI libraries. -.. _interfaces.py: https://github.com/letsencrypt/lets-encrypt-preview/blob/master/letsencrypt/interfaces.py +.. _interfaces.py: https://github.com/letsencrypt/letsencrypt/blob/master/letsencrypt/interfaces.py Authenticators diff --git a/docs/pkgs.rst b/docs/pkgs.rst index 8119ffc7e..2e1b18dfb 100644 --- a/docs/pkgs.rst +++ b/docs/pkgs.rst @@ -6,7 +6,7 @@ Packages described in `#358`_. For the time being those packages are bundled together into a single repo, and single documentation. -.. _`#358`: https://github.com/letsencrypt/lets-encrypt-preview/issues/358 +.. _`#358`: https://github.com/letsencrypt/letsencrypt/issues/358 .. toctree:: :glob: diff --git a/docs/using.rst b/docs/using.rst index 96eb62b05..951c991d2 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -14,7 +14,7 @@ server that the domain your requesting a cert for resolves to, sudo docker run -it --rm -p 443:443 --name letsencrypt \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ - quay.io/letsencrypt/lets-encrypt-preview:latest + quay.io/letsencrypt/letsencrypt:latest and follow the instructions. Your new cert will be available in ``/etc/letsencrypt/certs``. @@ -30,8 +30,8 @@ Please `install Git`_ and run the following commands: .. code-block:: shell - git clone https://github.com/letsencrypt/lets-encrypt-preview - cd lets-encrypt-preview + git clone https://github.com/letsencrypt/letsencrypt + cd letsencrypt Alternatively you could `download the ZIP archive`_ and extract the snapshot of our repository, but it's strongly recommended to use the @@ -39,7 +39,7 @@ above method instead. .. _`install Git`: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git .. _`download the ZIP archive`: - https://github.com/letsencrypt/lets-encrypt-preview/archive/master.zip + https://github.com/letsencrypt/letsencrypt/archive/master.zip Prerequisites @@ -76,7 +76,7 @@ For squeeze you will need to: - Use ``virtualenv --no-site-packages -p python`` instead of ``-p python2``. -.. _`#280`: https://github.com/letsencrypt/lets-encrypt-preview/issues/280 +.. _`#280`: https://github.com/letsencrypt/letsencrypt/issues/280 Mac OSX