Also move plugin selection logic from display.ops

This commit is contained in:
Peter Eckersley
2016-03-11 15:24:57 -08:00
parent 1a233d39be
commit c6fece8b40
4 changed files with 143 additions and 141 deletions
+2 -3
View File
@@ -11,8 +11,6 @@ from acme import client as acme_client
from acme import jose
from acme import messages
import letsencrypt
from letsencrypt import account
from letsencrypt import auth_handler
from letsencrypt import configuration
@@ -27,6 +25,7 @@ from letsencrypt import storage
from letsencrypt.display import ops as display_ops
from letsencrypt.display import enhancements
from letsencrypt.plugins import selection as plugin_selection
logger = logging.getLogger(__name__)
@@ -524,7 +523,7 @@ def rollback(default_installer, checkpoints, config, plugins):
"""
# Misconfigurations are only a slight problems... allow the user to rollback
installer = display_ops.pick_installer(
installer = plugin_selection.pick_installer(
config, default_installer, plugins, question="Which installer "
"should be used for rollback?")
+9 -127
View File
@@ -8,131 +8,13 @@ from letsencrypt import errors
from letsencrypt import interfaces
from letsencrypt import le_util
from letsencrypt.display import util as display_util
import letsencrypt.plugins.selection
logger = logging.getLogger(__name__)
# Define a helper function to avoid verbose code
util = zope.component.getUtility
def choose_plugin(prepared, question):
"""Allow the user to choose their plugin.
:param list prepared: List of `~.PluginEntryPoint`.
:param str question: Question to be presented to the user.
:returns: Plugin entry point chosen by the user.
:rtype: `~.PluginEntryPoint`
"""
opts = [plugin_ep.description_with_name +
(" [Misconfigured]" if plugin_ep.misconfigured else "")
for plugin_ep in prepared]
while True:
disp = util(interfaces.IDisplay)
code, index = disp.menu(question, opts, help_label="More Info")
if code == display_util.OK:
plugin_ep = prepared[index]
if plugin_ep.misconfigured:
util(interfaces.IDisplay).notification(
"The selected plugin encountered an error while parsing "
"your server configuration and cannot be used. The error "
"was:\n\n{0}".format(plugin_ep.prepare()),
height=display_util.HEIGHT, pause=False)
else:
return plugin_ep
elif code == display_util.HELP:
if prepared[index].misconfigured:
msg = "Reported Error: %s" % prepared[index].prepare()
else:
msg = prepared[index].init().more_info()
util(interfaces.IDisplay).notification(
msg, height=display_util.HEIGHT)
else:
return None
def pick_plugin(config, default, plugins, question, ifaces):
"""Pick plugin.
:param letsencrypt.interfaces.IConfig: Configuration
:param str default: Plugin name supplied by user or ``None``.
:param letsencrypt.plugins.disco.PluginsRegistry plugins:
All plugins registered as entry points.
:param str question: Question to be presented to the user in case
multiple candidates are found.
:param list ifaces: Interfaces that plugins must provide.
:returns: Initialized plugin.
:rtype: IPlugin
"""
if default is not None:
# throw more UX-friendly error if default not in plugins
filtered = plugins.filter(lambda p_ep: p_ep.name == default)
else:
if config.noninteractive_mode:
# it's really bad to auto-select the single available plugin in
# non-interactive mode, because an update could later add a second
# available plugin
raise errors.MissingCommandlineFlag(
"Missing command line flags. For non-interactive execution, "
"you will need to specify a plugin on the command line. Run "
"with '--help plugins' to see a list of options, and see "
"https://eff.org/letsencrypt-plugins for more detail on what "
"the plugins do and how to use them.")
filtered = plugins.visible().ifaces(ifaces)
filtered.init(config)
verified = filtered.verify(ifaces)
verified.prepare()
prepared = verified.available()
if len(prepared) > 1:
logger.debug("Multiple candidate plugins: %s", prepared)
plugin_ep = choose_plugin(prepared.values(), question)
if plugin_ep is None:
return None
else:
return plugin_ep.init()
elif len(prepared) == 1:
plugin_ep = prepared.values()[0]
logger.debug("Single candidate plugin: %s", plugin_ep)
if plugin_ep.misconfigured:
return None
return plugin_ep.init()
else:
logger.debug("No candidate plugin")
return None
def pick_authenticator(
config, default, plugins, question="How would you "
"like to authenticate with the Let's Encrypt CA?"):
"""Pick authentication plugin."""
return pick_plugin(
config, default, plugins, question, (interfaces.IAuthenticator,))
def pick_installer(config, default, plugins,
question="How would you like to install certificates?"):
"""Pick installer plugin."""
return pick_plugin(
config, default, plugins, question, (interfaces.IInstaller,))
def pick_configurator(
config, default, plugins,
question="How would you like to authenticate and install "
"certificates?"):
"""Pick configurator plugin."""
return pick_plugin(
config, default, plugins, question,
(interfaces.IAuthenticator, interfaces.IInstaller))
z_util = zope.component.getUtility
def get_email(more=False, invalid=False):
@@ -182,7 +64,7 @@ def choose_account(accounts):
# Note this will get more complicated once we start recording authorizations
labels = [acc.slug for acc in accounts]
code, index = util(interfaces.IDisplay).menu(
code, index = z_util(interfaces.IDisplay).menu(
"Please choose an account", labels)
if code == display_util.OK:
return accounts[index]
@@ -208,7 +90,7 @@ def choose_names(installer):
names = get_valid_domains(domains)
if not names:
manual = util(interfaces.IDisplay).yesno(
manual = z_util(interfaces.IDisplay).yesno(
"No names were found in your configuration files.{0}You should "
"specify ServerNames in your config files in order to allow for "
"accurate installation of your certificate.{0}"
@@ -256,7 +138,7 @@ def _filter_names(names):
:rtype: tuple
"""
code, names = util(interfaces.IDisplay).checklist(
code, names = z_util(interfaces.IDisplay).checklist(
"Which names would you like to activate HTTPS for?",
tags=names, cli_flag="--domains")
return code, [str(s) for s in names]
@@ -265,7 +147,7 @@ def _filter_names(names):
def _choose_names_manually():
"""Manually input names for those without an installer."""
code, input_ = util(interfaces.IDisplay).input(
code, input_ = z_util(interfaces.IDisplay).input(
"Please enter in your domain name(s) (comma and/or space separated) ",
cli_flag="--domains")
@@ -300,7 +182,7 @@ def _choose_names_manually():
if retry_message:
# We had error in input
retry = util(interfaces.IDisplay).yesno(retry_message)
retry = z_util(interfaces.IDisplay).yesno(retry_message)
if retry:
return _choose_names_manually()
else:
@@ -316,7 +198,7 @@ def success_installation(domains):
:param list domains: domain names which were enabled
"""
util(interfaces.IDisplay).notification(
z_util(interfaces.IDisplay).notification(
"Congratulations! You have successfully enabled {0}{1}{1}"
"You should test your configuration at:{1}{2}".format(
_gen_https_names(domains),
@@ -335,7 +217,7 @@ def success_renewal(domains, action):
:param str action: can be "reinstall" or "renew"
"""
util(interfaces.IDisplay).notification(
z_util(interfaces.IDisplay).notification(
"Your existing certificate has been successfully {3}ed, and the "
"new certificate has been installed.{1}{1}"
"The new certificate covers the following domains: {0}{1}{1}"
+126 -5
View File
@@ -1,7 +1,128 @@
from __future__ import print_function
import os
from letsencrypt import errors
from letsencrypt.display import ops as display_ops
from letsencrypt import errors, interfaces
from letsencrypt.display import util as display_util
logger = logging.getLogger(__name__)
z_util = zope.component.getUtility
def pick_configurator(
config, default, plugins,
question="How would you like to authenticate and install "
"certificates?"):
"""Pick configurator plugin."""
return pick_plugin(
config, default, plugins, question,
(interfaces.IAuthenticator, interfaces.IInstaller))
def pick_installer(config, default, plugins,
question="How would you like to install certificates?"):
"""Pick installer plugin."""
return pick_plugin(
config, default, plugins, question, (interfaces.IInstaller,))
def pick_authenticator(
config, default, plugins, question="How would you "
"like to authenticate with the Let's Encrypt CA?"):
"""Pick authentication plugin."""
return pick_plugin(
config, default, plugins, question, (interfaces.IAuthenticator,))
def pick_plugin(config, default, plugins, question, ifaces):
"""Pick plugin.
:param letsencrypt.interfaces.IConfig: Configuration
:param str default: Plugin name supplied by user or ``None``.
:param letsencrypt.plugins.disco.PluginsRegistry plugins:
All plugins registered as entry points.
:param str question: Question to be presented to the user in case
multiple candidates are found.
:param list ifaces: Interfaces that plugins must provide.
:returns: Initialized plugin.
:rtype: IPlugin
"""
if default is not None:
# throw more UX-friendly error if default not in plugins
filtered = plugins.filter(lambda p_ep: p_ep.name == default)
else:
if config.noninteractive_mode:
# it's really bad to auto-select the single available plugin in
# non-interactive mode, because an update could later add a second
# available plugin
raise errors.MissingCommandlineFlag(
"Missing command line flags. For non-interactive execution, "
"you will need to specify a plugin on the command line. Run "
"with '--help plugins' to see a list of options, and see "
"https://eff.org/letsencrypt-plugins for more detail on what "
"the plugins do and how to use them.")
filtered = plugins.visible().ifaces(ifaces)
filtered.init(config)
verified = filtered.verify(ifaces)
verified.prepare()
prepared = verified.available()
if len(prepared) > 1:
logger.debug("Multiple candidate plugins: %s", prepared)
plugin_ep = choose_plugin(prepared.values(), question)
if plugin_ep is None:
return None
else:
return plugin_ep.init()
elif len(prepared) == 1:
plugin_ep = prepared.values()[0]
logger.debug("Single candidate plugin: %s", plugin_ep)
if plugin_ep.misconfigured:
return None
return plugin_ep.init()
else:
logger.debug("No candidate plugin")
return None
def choose_plugin(prepared, question):
"""Allow the user to choose their plugin.
:param list prepared: List of `~.PluginEntryPoint`.
:param str question: Question to be presented to the user.
:returns: Plugin entry point chosen by the user.
:rtype: `~.PluginEntryPoint`
"""
opts = [plugin_ep.description_with_name +
(" [Misconfigured]" if plugin_ep.misconfigured else "")
for plugin_ep in prepared]
while True:
disp = z_util(interfaces.IDisplay)
code, index = disp.menu(question, opts, help_label="More Info")
if code == display_util.OK:
plugin_ep = prepared[index]
if plugin_ep.misconfigured:
z_util(interfaces.IDisplay).notification(
"The selected plugin encountered an error while parsing "
"your server configuration and cannot be used. The error "
"was:\n\n{0}".format(plugin_ep.prepare()),
height=display_util.HEIGHT, pause=False)
else:
return plugin_ep
elif code == display_util.HELP:
if prepared[index].misconfigured:
msg = "Reported Error: %s" % prepared[index].prepare()
else:
msg = prepared[index].init().more_info()
z_util(interfaces.IDisplay).notification(
msg, height=display_util.HEIGHT)
else:
return None
logger = logging.getLogger(__name__)
@@ -54,12 +175,12 @@ def choose_configurator_plugins(config, plugins, verb):
if verb == "run" and req_auth == req_inst:
# Unless the user has explicitly asked for different auth/install,
# only consider offering a single choice
authenticator = installer = display_ops.pick_configurator(config, req_inst, plugins)
authenticator = installer = pick_configurator(config, req_inst, plugins)
else:
if need_inst or req_inst:
installer = display_ops.pick_installer(config, req_inst, plugins)
installer = pick_installer(config, req_inst, plugins)
if need_auth:
authenticator = display_ops.pick_authenticator(config, req_auth, plugins)
authenticator = pick_authenticator(config, req_auth, plugins)
logger.debug("Selected authenticator %s and installer %s", authenticator, installer)
# Report on any failures
+6 -6
View File
@@ -76,7 +76,7 @@ class PickPluginTest(unittest.TestCase):
self.ifaces = []
def _call(self):
from letsencrypt.display.ops import pick_plugin
from letsencrypt.plugins.selection import pick_plugin
return pick_plugin(self.config, self.default, self.reg,
self.question, self.ifaces)
@@ -149,17 +149,17 @@ class ConveniencePickPluginTest(unittest.TestCase):
config, default, plugins, "Question?", ifaces)
def test_authenticator(self):
from letsencrypt.display.ops import pick_authenticator
from letsencrypt.plugins.selection import pick_authenticator
self._test(pick_authenticator, (interfaces.IAuthenticator,))
def test_installer(self):
from letsencrypt.display.ops import pick_installer
from letsencrypt.plugins.selection import pick_installer
self._test(pick_installer, (interfaces.IInstaller,))
def test_configurator(self):
from letsencrypt.display.ops import pick_configurator
self._test(pick_configurator, (
interfaces.IAuthenticator, interfaces.IInstaller))
from letsencrypt.plugins.selection import pick_configurator
self._test(pick_configurator,
(interfaces.IAuthenticator, interfaces.IInstaller))
class GetEmailTest(unittest.TestCase):