diff --git a/letsencrypt-nginx/tests/boulder-integration.sh b/letsencrypt-nginx/tests/boulder-integration.sh index 0e3e7e77a..3cbe9f6b9 100755 --- a/letsencrypt-nginx/tests/boulder-integration.sh +++ b/letsencrypt-nginx/tests/boulder-integration.sh @@ -18,7 +18,7 @@ letsencrypt_test_nginx () { "$@" } -letsencrypt_test_nginx --domain nginx.wtf run +letsencrypt_test_nginx --domains nginx.wtf run echo | openssl s_client -connect localhost:5001 \ | openssl x509 -out $root/nginx.pem diff -q $root/nginx.pem $root/conf/live/nginx.wtf/cert.pem diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index b6181bb64..fa743a770 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -106,7 +106,7 @@ def _find_domains(args, installer): domains = args.domains if not domains: - raise errors.Error("Please specify --domain, or --installer that " + raise errors.Error("Please specify --domains, or --installer that " "will help in domain names autodiscovery") return domains @@ -465,9 +465,9 @@ def obtaincert(args, config, plugins): """Authenticate & obtain cert, but do not install it.""" if args.domains is not None and args.csr is not None: - # TODO: --csr could have a priority, when --domain is + # TODO: --csr could have a priority, when --domains is # supplied, check if CSR matches given domains? - return "--domain and --csr are mutually exclusive" + return "--domains and --csr are mutually exclusive" try: # installers are used in auth mode to determine domain names @@ -672,8 +672,32 @@ class HelpfulArgumentParser(object): parsed_args = self.parser.parse_args(self.args) parsed_args.func = self.VERBS[self.verb] + parsed_args.domains = self._parse_domains(parsed_args.domains) return parsed_args + def _parse_domains(self, domains): + """Helper function for parse_args() that parses domains from a + (possibly) comma separated list and returns list of unique domains. + + :param domains: List of domain flags + :type domains: `list` of `string` + + :returns: List of unique domains + :rtype: `list` of `string` + + """ + + uniqd = None + + if domains: + dlist = [] + for domain in domains: + dlist.extend([d.strip() for d in domain.split(",")]) + # Make sure we don't have duplicates + uniqd = [d for i, d in enumerate(dlist) if d not in dlist[:i]] + + return uniqd + def determine_verb(self): """Determines the verb/subcommand provided by the user. @@ -811,14 +835,15 @@ def prepare_and_parse_args(plugins, args): None, "-t", "--text", dest="text_mode", action="store_true", help="Use the text output instead of the curses UI.") helpful.add(None, "-m", "--email", help=config_help("email")) - # positional arg shadows --domain, instead of appending, and - # --domain is useful, because it can be stored in config + # 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") - helpful.add(None, "-d", "--domain", dest="domains", + helpful.add(None, "-d", "--domains", dest="domains", metavar="DOMAIN", action="append", - help="Domain names to apply. Use multiple -d flags if you want " - "to specify multiple domains") + help="Domain names to apply. For multiple domains you can use " + "multiple -d flags or enter a comma separated list of domains" + "as a parameter.") helpful.add( None, "--duplicate", dest="duplicate", action="store_true", help="Allow getting a certificate that duplicates an existing one") diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 24c2e9097..811d11da8 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -135,7 +135,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods @mock.patch('letsencrypt.cli.display_ops') def test_installer_selection(self, mock_display_ops): - self._call(['install', '--domain', 'foo.bar', '--cert-path', 'cert', + self._call(['install', '--domains', 'foo.bar', '--cert-path', 'cert', '--key-path', 'key', '--chain-path', 'chain']) self.assertEqual(mock_display_ops.pick_installer.call_count, 1) @@ -249,7 +249,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods def test_certonly_bad_args(self): ret, _, _, _ = self._call(['-d', 'foo.bar', 'certonly', '--csr', CSR]) - self.assertEqual(ret, '--domain and --csr are mutually exclusive') + self.assertEqual(ret, '--domains and --csr are mutually exclusive') ret, _, _, _ = self._call(['-a', 'bad_auth', 'certonly']) self.assertEqual(ret, 'The requested bad_auth plugin does not appear to be installed') @@ -272,6 +272,27 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods self._call, ['-d', '*.wildcard.tld']) + def test_parse_domains(self): + from letsencrypt import cli + plugins = disco.PluginsRegistry.find_all() + + short_args = ['-d', 'example.com'] + namespace = cli.prepare_and_parse_args(plugins, short_args) + self.assertEqual(namespace.domains, ['example.com']) + + short_args = ['-d', 'example.com,another.net,third.org,example.com'] + namespace = cli.prepare_and_parse_args(plugins, short_args) + self.assertEqual(namespace.domains, ['example.com', 'another.net', + 'third.org']) + + long_args = ['--domains', 'example.com'] + namespace = cli.prepare_and_parse_args(plugins, long_args) + self.assertEqual(namespace.domains, ['example.com']) + + long_args = ['--domains', 'example.com,another.net,example.com'] + namespace = cli.prepare_and_parse_args(plugins, long_args) + self.assertEqual(namespace.domains, ['example.com', 'another.net']) + @mock.patch('letsencrypt.crypto_util.notAfter') @mock.patch('letsencrypt.cli.zope.component.getUtility') def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter): diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh index 97babb591..53996cd20 100755 --- a/tests/boulder-integration.sh +++ b/tests/boulder-integration.sh @@ -27,8 +27,8 @@ common() { "$@" } -common --domain le1.wtf --standalone-supported-challenges tls-sni-01 auth -common --domain le2.wtf --standalone-supported-challenges http-01 run +common --domains le1.wtf --standalone-supported-challenges tls-sni-01 auth +common --domains le2.wtf --standalone-supported-challenges http-01 run common -a manual -d le.wtf auth export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \ @@ -40,7 +40,7 @@ common auth --csr "$CSR_PATH" \ openssl x509 -in "${root}/csr/0000_cert.pem" -text openssl x509 -in "${root}/csr/0000_chain.pem" -text -common --domain le3.wtf install \ +common --domains le3.wtf install \ --cert-path "${root}/csr/cert.pem" \ --key-path "${root}/csr/key.pem"