PluginsEntryPoint.prepare, move pick_* to display_ops

This commit is contained in:
Jakub Warmuz
2015-05-03 07:56:49 +00:00
parent 2ffc3c37cb
commit 595230fd8e
9 changed files with 146 additions and 111 deletions
@@ -10,18 +10,22 @@ from letsencrypt.client.plugins import common
class Authenticator(common.Plugin):
"""Example Authenticator."""
zope.interface.implements(interfaces.IAuthenticator)
zope.interface.classProvides(interfaces.IPluginFactory)
description = 'Example Authenticator plugin'
description = "Example Authenticator plugin"
# Implement all methods from IAuthenticator, remembering to add
# "self" as first argument, e.g. def prepare(self)...
class Installer(common.Plugins):
"""Example Installer."""
zope.interface.implements(interfaces.IInstaller)
zope.interface.classProvides(interfaces.IPluginFactory)
description = 'Example Installer plugin'
description = "Example Installer plugin"
# Implement all methods from IInstaller, remembering to add
# "self" as first argument, e.g. def get_all_names(self)...
+9 -10
View File
@@ -89,14 +89,14 @@ def run(args, config, plugins):
"pair, but not both, is allowed")
if args.authenticator is not None or args.installer is not None:
installer = plugins_disco.pick_installer(
installer = display_ops.pick_installer(
config, args.installer, plugins)
authenticator = plugins_disco.pick_authenticator(
authenticator = display_ops.pick_authenticator(
config, args.authenticator, plugins)
else:
# TODO: this assume that user doesn't want to pick authenticator
# and installer separately...
authenticator = installer = plugins_disco.pick_configurator(
authenticator = installer = display_ops.pick_configurator(
config, args.configurator, plugins)
if installer is None or authenticator is None:
@@ -114,13 +114,13 @@ def auth(args, config, plugins):
if acc is None:
return None
authenticator = plugins_disco.pick_authenticator(
authenticator = display_ops.pick_authenticator(
config, args.authenticator, plugins)
if authenticator is None:
return "Authenticator could not be determined"
if args.installer is not None:
installer = plugins_disco.pick_installer(config, args.installer, plugins)
installer = display_ops.pick_installer(config, args.installer, plugins)
else:
installer = None
@@ -135,7 +135,7 @@ def install(args, config, plugins):
if acc is None:
return None
installer = plugins_disco.pick_installer(config, args.installer, plugins)
installer = display_ops.pick_installer(config, args.installer, plugins)
if installer is None:
return "Installer could not be determined"
acme, doms = _common_run(
@@ -214,11 +214,10 @@ def plugins_cmd(args, config, plugins):
if not args.prepare:
return _print_plugins(filtered)
prepared = plugins_disco.prepare_plugins(initialized)
logging.debug("Prepared plugins: %s", plugins)
available = plugins_disco.available_plugins(filtered)
logging.debug("Prepared plugins: %s", available)
_print_plugins(prepared, plugins, names)
plugins_disco
_print_plugins(available)
def read_file(filename):
+2 -4
View File
@@ -20,8 +20,6 @@ from letsencrypt.client import network2
from letsencrypt.client import reverter
from letsencrypt.client import revoker
from letsencrypt.client.plugins import disco as plugins_disco
from letsencrypt.client.plugins.apache import configurator
from letsencrypt.client.display import ops as display_ops
@@ -349,7 +347,7 @@ def rollback(default_installer, checkpoints, config, plugins):
"""
# Misconfigurations are only a slight problems... allow the user to rollback
installer = plugins_disco.pick_installer(
installer = display_ops.pick_installer(
config, default_installer, plugins, question="Which installer "
"should be used for rollback?")
@@ -368,7 +366,7 @@ def revoke(default_installer, config, plugins, no_confirm, cert, authkey):
:type config: :class:`letsencrypt.client.interfaces.IConfig`
"""
installer = plugins_disco.pick_installer(
installer = display_ops.pick_installer(
config, default_installer, plugins, question="Which installer "
"should be used for certificate revocation?")
+51 -3
View File
@@ -6,6 +6,7 @@ import zope.component
from letsencrypt.client import interfaces
from letsencrypt.client.display import util as display_util
from letsencrypt.client.plugins import disco as plugins_disco
# Define a helper function to avoid verbose code
util = zope.component.getUtility # pylint: disable=invalid-name
@@ -17,9 +18,9 @@ def choose_plugin(prepared, question):
:param list prepared:
"""
opts = [plugin_ep.name_with_description if error is None
opts = [plugin_ep.name_with_description if not plugin_ep.misconfigured
else "%s (Misconfigured)" % plugin_ep.name_with_description
for (plugin_ep, error) in prepared]
for plugin_ep in prepared.itervalues()]
while True:
code, index = util(interfaces.IDisplay).menu(
@@ -29,7 +30,7 @@ def choose_plugin(prepared, question):
return prepared[index][0]
elif code == display_util.HELP:
if prepared[index][1] is not None:
msg = "Reported Error: %s" % prepared[index][1]
msg = "Reported Error: %s" % prepared[index].prepare()
else:
msg = prepared[index][0].init().more_info()
util(interfaces.IDisplay).notification(
@@ -37,6 +38,53 @@ def choose_plugin(prepared, question):
else:
return None
def _pick_plugin(config, default, plugins, question, ifaces):
if default is not None:
filtered = {default: plugins[default]}
else:
filtered = plugins.filter(ifaces)
for plugin_ep in plugins.itervalues():
plugin_ep.init(config)
verified = plugins_disco.verify_plugins(filtered, ifaces)
prepared = plugins_disco.available_plugins(verified)
if len(prepared) > 1:
logging.debug("Multiple candidate plugins: %s", prepared)
return choose_plugin(prepared.values(), question).init()
elif len(prepared) == 1:
plugin_ep = prepared.values()[0]
logging.debug("Single candidate plugin: %s", plugin_ep)
return plugin_ep.init()
else:
logging.debug("No candidate plugin")
return None
def pick_authenticator(
config, default, plugins, question="How would you "
"like to authenticate with 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))
def choose_account(accounts):
"""Choose an account.
+4 -2
View File
@@ -69,9 +69,11 @@ class IPlugin(zope.interface.Interface):
Finish up any additional initialization.
:raises letsencrypt.client.errors.LetsEncryptMisconfigurationError:
when full initialization cannot be completed.
when full initialization cannot be completed. Plugin will be
displayed on a list of available plugins.
:raises letsencrypt.client.errors.LetsEncryptNoInstallationError:
when the necessary programs/files cannot be located.
when the necessary programs/files cannot be located. Plugin
will NOT be displayed on a list of available plugins.
"""
+2 -1
View File
@@ -18,7 +18,8 @@ def dest_namespace(name):
class Plugin(object):
"""Generic plugin."""
zope.interface.implements(interfaces.IPlugin)
zope.interface.classProvides(interfaces.IPluginFactory)
# classProvides is not inherited, subclasses must define it on their own
#zope.interface.classProvides(interfaces.IPluginFactory)
def __init__(self, config, name):
self.config = config
+68 -87
View File
@@ -9,8 +9,6 @@ from letsencrypt.client import constants
from letsencrypt.client import errors
from letsencrypt.client import interfaces
from letsencrypt.client.display import ops as display_ops
class PluginEntryPoint(object):
"""Plugin entry point."""
@@ -23,11 +21,7 @@ class PluginEntryPoint(object):
self.plugin_cls = entry_point.load()
self.entry_point = entry_point
self._initialized = None
@property
def initialized(self):
"""Has the plugin been initialized already?"""
return self._initialized is not None
self._prepared = None
@classmethod
def entry_point_to_plugin_name(cls, entry_point):
@@ -36,6 +30,11 @@ class PluginEntryPoint(object):
return entry_point.name
return entry_point.dist.key + ":" + entry_point.name
@property
def initialized(self):
"""Has the plugin been initialized already?"""
return self._initialized is not None
def init(self, config=None):
"""Memoized plugin inititialization."""
if not self.initialized:
@@ -43,6 +42,39 @@ class PluginEntryPoint(object):
self._initialized = self.plugin_cls(config, self.name)
return self._initialized
@property
def prepared(self):
"""Has the plugin been prepared already?"""
if not self.initialized:
logging.debug(".prepared called on uninitialized %s", self)
return self._prepared is not None
def prepare(self):
"""Memoized plugin preparation."""
assert self.initialized
if self._prepared is None:
try:
self._initialized.prepare()
except errors.LetsEncryptMisconfigurationError as error:
logging.debug("Misconfigured %s: %s", self, error)
self._prepared = error
except errors.LetsEncryptNoInstallationError as error:
logging.debug("No installation (%s): %s", self, error)
self._prepared = error
else:
self._prepared = True
return self._prepared
@property
def misconfigured(self):
"""Is plugin misconfigured?"""
return isinstance(self._prepared, errors.LetsEncryptMisconfigurationError)
@property
def available(self):
"""Is plugin available, i.e. prepared or misconfigured?"""
return self._prepared is True or self.misconfigured
def __repr__(self):
return "PluginEntryPoint#{0}".format(self.name)
@@ -51,6 +83,20 @@ class PluginEntryPoint(object):
"""Name with description. Handy for UI."""
return "{0} ({1})".format(self.name, self.plugin_cls.description)
def verify(self, ifaces):
assert self.initialized
for iface in ifaces: # zope.interface.providedBy(plugin)
try:
zope.interface.verify.verifyObject(iface, self.init())
except zope.interface.exceptions.BrokenImplementation:
if iface.implementedBy(self.plugin_cls):
logging.debug(
"%s implements %s but object does "
"not verify", self.plugin_cls, iface.__name__)
return False
return True
class PluginsRegistry(collections.Mapping):
"""Plugins registry."""
@@ -66,8 +112,12 @@ class PluginsRegistry(collections.Mapping):
constants.SETUPTOOLS_PLUGINS_ENTRY_POINT):
plugin_ep = PluginEntryPoint(entry_point)
assert plugin_ep.name not in plugins, (
"PREFIX_FREE_DISTRIBTIONS messed up")
plugins[plugin_ep.name] = plugin_ep
"PREFIX_FREE_DISTRIBUTIONS messed up")
if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls):
plugins[plugin_ep.name] = plugin_ep
else:
logging.warning("Plugin entry point %s does not provide "
"IPluginFactory, skipping", plugin_ep)
return cls(plugins)
def filter(self, *ifaces_groups):
@@ -81,7 +131,8 @@ class PluginsRegistry(collections.Mapping):
for ifaces in ifaces_groups)))
def __repr__(self):
return "{0}({1!r})".format(self.__class__.__name__, self.plugins)
return "{0}({1!r})".format(
self.__class__.__name__, set(self.plugins.itervalues()))
def __getitem__(self, name):
return self.plugins[name]
@@ -95,85 +146,15 @@ class PluginsRegistry(collections.Mapping):
def verify_plugins(initialized, ifaces):
"""Verify plugin objects."""
verified = {}
for name, plugin_ep in initialized.iteritems():
verifies = True
for iface in ifaces: # zope.interface.providedBy(plugin)
try:
zope.interface.verify.verifyObject(iface, plugin_ep.init())
except zope.interface.exceptions.BrokenImplementation:
if iface.implementedBy(plugin_ep.plugin_cls):
logging.debug(
"%s implements %s but object does "
"not verify", plugin_ep.plugin_cls, iface.__name__)
verifies = False
break
if verifies:
verified[name] = plugin_ep
return verified
return dict((name, plugin_ep) for name, plugin_ep in initialized.iteritems()
if plugin_ep.verify(ifaces))
def prepare_plugins(initialized):
"""Prepare plugins."""
def available_plugins(initialized):
"""Prepare plugins and filter available."""
prepared = {}
for name, plugin_ep in initialized.iteritems():
error = None
try:
plugin_ep.init().prepare()
except errors.LetsEncryptMisconfigurationError as error:
logging.debug("Misconfigured %s: %s", plugin_ep, error)
except errors.LetsEncryptNoInstallationError as error:
logging.debug("No installation (%s): %s", plugin_ep, error)
continue
prepared[name] = (plugin_ep, error)
plugin_ep.prepare()
if plugin_ep.available:
prepared[name] = plugin_ep
return prepared # succefully prepared + misconfigured
def _pick_plugin(config, default, plugins, question, ifaces):
if default is not None:
filtered = {default: plugins[default]}
else:
filtered = plugins.filter(ifaces)
for plugin_ep in plugins.itervalues():
plugin_ep.init(config)
verified = verify_plugins(filtered, ifaces)
prepared = prepare_plugins(verified)
if len(prepared) > 1:
logging.debug("Multiple candidate plugins: %s", prepared)
return display_ops.choose_plugin(prepared.values(), question).init()
elif len(prepared) == 1:
plugin_ep = prepared.values()[0][0]
logging.debug("Single candidate plugin: %s", plugin_ep)
return plugin_ep.init()
else:
logging.debug("No candidate plugin")
return None
def pick_authenticator(
config, default, plugins, question="How would you "
"like to authenticate with 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))
@@ -31,6 +31,8 @@ class StandaloneAuthenticator(common.Plugin):
"""
zope.interface.implements(interfaces.IAuthenticator)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Standalone Authenticator"
def __init__(self, *args, **kwargs):
+2 -2
View File
@@ -66,7 +66,7 @@ class RollbackTest(unittest.TestCase):
from letsencrypt.client.client import rollback
rollback(None, checkpoints, {}, mock.MagicMock())
@mock.patch("letsencrypt.client.client.plugins_disco.pick_installer")
@mock.patch("letsencrypt.client.client.display_ops.pick_installer")
def test_no_problems(self, mock_pick_installer):
mock_pick_installer.side_effect = self.m_install
@@ -75,7 +75,7 @@ class RollbackTest(unittest.TestCase):
self.assertEqual(self.m_install().rollback_checkpoints.call_count, 1)
self.assertEqual(self.m_install().restart.call_count, 1)
@mock.patch("letsencrypt.client.client.plugins_disco.pick_installer")
@mock.patch("letsencrypt.client.client.display_ops.pick_installer")
def test_no_installer(self, mock_pick_installer):
mock_pick_installer.return_value = None
self._call(1)