diff --git a/docs/using.rst b/docs/using.rst index 687901191..1423d6eba 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -31,16 +31,21 @@ Firstly, please `install Git`_ and run the following commands: .. _`install Git`: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +.. note:: On RedHat/CentOS 6 you will need to enable the EPEL_ + repository before install. + +.. _EPEL: http://fedoraproject.org/wiki/EPEL + To install and run the client you just need to type: .. code-block:: shell ./letsencrypt-auto -.. note:: On RedHat/CentOS 6 you will need to enable the EPEL_ - repository before install. - -.. _EPEL: http://fedoraproject.org/wiki/EPEL +.. hint:: During the beta phase, Let's Encrypt enforces strict rate limits on + the number of certificates issued for one domain. It is recommended to + initially use the test server via `--test-cert` until you get the desired + certificates. Throughout the documentation, whenever you see references to ``letsencrypt`` script/binary, you can substitute in @@ -58,8 +63,8 @@ or for full help, type: ``letsencrypt-auto`` is the recommended method of running the Let's Encrypt -client beta releases on systems that don't have a packaged version. Debian -experimental, Arch linux and FreeBSD now have native packages, so on those +client beta releases on systems that don't have a packaged version. Debian, +Arch linux and FreeBSD now have native packages, so on those systems you can just install ``letsencrypt`` (and perhaps ``letsencrypt-apache``). If you'd like to run the latest copy from Git, or run your own locally modified copy of the client, follow the instructions in @@ -173,10 +178,10 @@ Renewal In order to renew certificates simply call the ``letsencrypt`` (or letsencrypt-auto_) again, and use the same values when prompted. You can automate it slightly by passing necessary flags on the CLI (see -`--help all`), or even further using the :ref:`config-file`. The -``--renew-by-default`` flag may be helpful for automating renewal. If -you're sure that UI doesn't prompt for any details you can add the -command to ``crontab`` (make it less than every 90 days to avoid +`--help all`), or even further using the :ref:`config-file`. The +``--renew-by-default`` flag may be helpful for automating renewal. If +you're sure that UI doesn't prompt for any details you can add the +command to ``crontab`` (make it less than every 90 days to avoid problems, say every month). Please note that the CA will send notification emails to the address @@ -352,20 +357,20 @@ Operating System Packages sudo pacman -S letsencrypt letsencrypt-apache -**Debian Experimental** +**Debian** -If you run Debian unstable, you can install experimental letsencrypt packages. -Add the line ``deb http://ftp.us.debian.org/debian/ experimental main`` (or -the equivalent for your country) to ``/etc/apt/sources.list``, then run +If you run Debian Stretch or Debian Sid, you can install letsencrypt packages. .. code-block:: shell sudo apt-get update - sudo apt-get -t experimental install letsencrypt python-letsencrypt-apache + sudo apt-get install letsencrypt python-letsencrypt-apache If you don't want to use the Apache plugin, you can ommit the ``python-letsencrypt-apache`` package. +Packages for Debian Jessie are coming in the next few weeks. + **Other Operating Systems** OS packaging is an ongoing effort. If you'd like to package diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 5e06d00d6..29519d430 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1,4 +1,6 @@ """Let's Encrypt CLI.""" +from __future__ import print_function + # TODO: Sanity check all input. Be sure to avoid shell code etc... # pylint: disable=too-many-lines # (TODO: split this file into main.py and cli.py) @@ -660,7 +662,7 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print logger.debug("Filtered plugins: %r", filtered) if not args.init and not args.prepare: - print str(filtered) + print(str(filtered)) return filtered.init(config) @@ -668,13 +670,13 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print logger.debug("Verified plugins: %r", verified) if not args.prepare: - print str(verified) + print(str(verified)) return verified.prepare() available = verified.available() logger.debug("Prepared plugins: %s", available) - print str(available) + print(str(available)) def read_file(filename, mode="rb"): @@ -767,7 +769,7 @@ class HelpfulArgumentParser(object): self.help_arg = max(help1, help2) if self.help_arg is True: # just --help with no topic; avoid argparse altogether - print usage + print(usage) sys.exit(0) self.visible_topics = self.determine_help_topics(self.help_arg) self.groups = {} # elements are added by .add_group() @@ -880,12 +882,12 @@ class HelpfulArgumentParser(object): """ if self.visible_topics[topic]: - #print "Adding visible group " + topic + #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): @@ -897,7 +899,7 @@ class HelpfulArgumentParser(object): """ for name, plugin_ep in plugins.iteritems(): parser_or_group = self.add_group(name, description=plugin_ep.description) - #print parser_or_group + #print(parser_or_group) plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) def determine_help_topics(self, chosen_topic): diff --git a/letsencrypt/display/ops.py b/letsencrypt/display/ops.py index 102dbe3a0..5ceb7fcfc 100644 --- a/letsencrypt/display/ops.py +++ b/letsencrypt/display/ops.py @@ -123,6 +123,7 @@ def pick_configurator( config, default, plugins, question, (interfaces.IAuthenticator, interfaces.IInstaller)) + def get_email(more=False, invalid=False): """Prompt for valid email address. @@ -209,6 +210,7 @@ def choose_names(installer): else: return [] + def get_valid_domains(domains): """Helper method for choose_names that implements basic checks on domain names @@ -226,6 +228,7 @@ def get_valid_domains(domains): continue return valid_domains + def _filter_names(names): """Determine which names the user would like to select from a list. @@ -250,7 +253,41 @@ def _choose_names_manually(): "Please enter in your domain name(s) (comma and/or space separated) ") if code == display_util.OK: - return display_util.separate_list_input(input_) + invalid_domains = dict() + retry_message = "" + try: + domain_list = display_util.separate_list_input(input_) + except UnicodeEncodeError: + domain_list = [] + retry_message = ( + "Internationalized domain names are not presently " + "supported.{0}{0}Would you like to re-enter the " + "names?{0}").format(os.linesep) + + for domain in domain_list: + try: + le_util.check_domain_sanity(domain) + except errors.ConfigurationError as e: + invalid_domains[domain] = e.message + + if len(invalid_domains): + retry_message = ( + "One or more of the entered domain names was not valid:" + "{0}{0}").format(os.linesep) + for domain in invalid_domains: + retry_message = retry_message + "{1}: {2}{0}".format( + os.linesep, domain, invalid_domains[domain]) + retry_message = retry_message + ( + "{0}Would you like to re-enter the names?{0}").format( + os.linesep) + + if retry_message: + # We had error in input + retry = util(interfaces.IDisplay).yesno(retry_message) + if retry: + return _choose_names_manually() + else: + return domain_list return [] diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 0a490d447..3996cfe67 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -7,6 +7,8 @@ within lineages of successor certificates, according to configuration. .. todo:: Call new installer API to restart servers after deployment """ +from __future__ import print_function + import argparse import logging import os @@ -169,7 +171,7 @@ def main(cli_args=sys.argv[1:]): constants.CONFIG_DIRS_MODE, uid) for renewal_file in os.listdir(cli_config.renewal_configs_dir): - print "Processing", renewal_file + print("Processing " + renewal_file) try: # TODO: Before trying to initialize the RenewableCert object, # we could check here whether the combination of the config diff --git a/letsencrypt/reporter.py b/letsencrypt/reporter.py index 0905dfa54..c0c7856a7 100644 --- a/letsencrypt/reporter.py +++ b/letsencrypt/reporter.py @@ -1,4 +1,6 @@ """Collects and displays information to the user.""" +from __future__ import print_function + import collections import logging import os @@ -75,8 +77,8 @@ class Reporter(object): no_exception = sys.exc_info()[0] is None bold_on = sys.stdout.isatty() if bold_on: - print le_util.ANSI_SGR_BOLD - print 'IMPORTANT NOTES:' + print(le_util.ANSI_SGR_BOLD) + print('IMPORTANT NOTES:') first_wrapper = textwrap.TextWrapper( initial_indent=' - ', subsequent_indent=(' ' * 3)) next_wrapper = textwrap.TextWrapper( @@ -89,9 +91,9 @@ class Reporter(object): sys.stdout.write(le_util.ANSI_SGR_RESET) bold_on = False lines = msg.text.splitlines() - print first_wrapper.fill(lines[0]) + print(first_wrapper.fill(lines[0])) if len(lines) > 1: - print "\n".join( - next_wrapper.fill(line) for line in lines[1:]) + print("\n".join( + next_wrapper.fill(line) for line in lines[1:])) if bold_on: sys.stdout.write(le_util.ANSI_SGR_RESET) diff --git a/letsencrypt/tests/display/ops_test.py b/letsencrypt/tests/display/ops_test.py index 30183b955..31db47cce 100644 --- a/letsencrypt/tests/display/ops_test.py +++ b/letsencrypt/tests/display/ops_test.py @@ -397,6 +397,44 @@ class ChooseNamesTest(unittest.TestCase): self.assertEqual(get_valid_domains(all_invalid), []) self.assertEqual(len(get_valid_domains(two_valid)), 2) + @mock.patch("letsencrypt.display.ops.util") + def test_choose_manually(self, mock_util): + from letsencrypt.display.ops import _choose_names_manually + # No retry + mock_util().yesno.return_value = False + # IDN and no retry + mock_util().input.return_value = (display_util.OK, + "uniçodé.com") + self.assertEqual(_choose_names_manually(), []) + # IDN exception with previous mocks + with mock.patch("letsencrypt.display.util") as mock_sl: + uerror = UnicodeEncodeError('mock', u'', + 0, 1, 'mock') + mock_sl.separate_list_input.side_effect = uerror + self.assertEqual(_choose_names_manually(), []) + # Punycode and no retry + mock_util().input.return_value = (display_util.OK, + "xn--ls8h.tld") + self.assertEqual(_choose_names_manually(), []) + # non-FQDN and no retry + mock_util().input.return_value = (display_util.OK, + "notFQDN") + self.assertEqual(_choose_names_manually(), []) + # Two valid domains + mock_util().input.return_value = (display_util.OK, + ("example.com," + "valid.example.com")) + self.assertEqual(_choose_names_manually(), + ["example.com", "valid.example.com"]) + # Three iterations + mock_util().input.return_value = (display_util.OK, + "notFQDN") + yn = mock.MagicMock() + yn.side_effect = [True, True, False] + mock_util().yesno = yn + _choose_names_manually() + self.assertEqual(mock_util().yesno.call_count, 3) + class SuccessInstallationTest(unittest.TestCase): # pylint: disable=too-few-public-methods