mirror of
https://github.com/certbot/certbot.git
synced 2026-07-27 16:30:31 +02:00
PluginsEntryPoint.prepare, move pick_* to display_ops
This commit is contained in:
@@ -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)...
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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?")
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user