mirror of
https://github.com/certbot/certbot.git
synced 2026-08-03 03:41:53 +02:00
common.Plugin (with .conf(var), PluginEntryPoint, PluginRegistry
This commit is contained in:
@@ -1,18 +1,27 @@
|
|||||||
"""Example Let's Encrypt plugins."""
|
"""Example Let's Encrypt plugins.
|
||||||
|
|
||||||
|
For full examples, see `letsencrypt.client.plugins`.
|
||||||
|
|
||||||
|
"""
|
||||||
import zope.interface
|
import zope.interface
|
||||||
|
|
||||||
from letsencrypt.client import interfaces
|
from letsencrypt.client import interfaces
|
||||||
|
from letsencrypt.client.plugins import common
|
||||||
|
|
||||||
|
|
||||||
class Authenticator(object):
|
class Authenticator(common.Plugin):
|
||||||
zope.interface.implements(interfaces.IAuthenticator)
|
zope.interface.implements(interfaces.IAuthenticator)
|
||||||
|
|
||||||
description = 'Example Authenticator plugin'
|
description = 'Example Authenticator plugin'
|
||||||
|
|
||||||
def __init__(self, config):
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
# Implement all methods from IAuthenticator, remembering to add
|
# Implement all methods from IAuthenticator, remembering to add
|
||||||
# "self" as first argument, e.g. def prepare(self)...
|
# "self" as first argument, e.g. def prepare(self)...
|
||||||
|
|
||||||
# For full examples, see letsencrypt.client.plugins
|
|
||||||
|
class Installer(common.Plugins):
|
||||||
|
zope.interface.implements(interfaces.IInstaller)
|
||||||
|
|
||||||
|
description = 'Example Installer plugin'
|
||||||
|
|
||||||
|
# Implement all methods from IInstaller, remembering to add
|
||||||
|
# "self" as first argument, e.g. def get_all_names(self)...
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ setup(
|
|||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
'letsencrypt.plugins': [
|
'letsencrypt.plugins': [
|
||||||
'example = letsencrypt_example_plugins:Authenticator',
|
'example_authenticator = letsencrypt_example_plugins:Authenticator',
|
||||||
|
'example_installer = letsencrypt_example_plugins:Installer',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import logging
|
|||||||
import augeas
|
import augeas
|
||||||
|
|
||||||
from letsencrypt.client import reverter
|
from letsencrypt.client import reverter
|
||||||
|
from letsencrypt.client.plugins import common
|
||||||
|
|
||||||
|
|
||||||
class AugeasConfigurator(object):
|
class AugeasConfigurator(common.Plugin):
|
||||||
"""Base Augeas Configurator class.
|
"""Base Augeas Configurator class.
|
||||||
|
|
||||||
:ivar config: Configuration.
|
:ivar config: Configuration.
|
||||||
@@ -21,8 +22,8 @@ class AugeasConfigurator(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, *args, **kwargs):
|
||||||
self.config = config
|
super(AugeasConfigurator, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Set Augeas flags to not save backup (we do it ourselves)
|
# Set Augeas flags to not save backup (we do it ourselves)
|
||||||
# Set Augeas to not load anything by default
|
# Set Augeas to not load anything by default
|
||||||
@@ -34,7 +35,7 @@ class AugeasConfigurator(object):
|
|||||||
# This needs to occur before VirtualHost objects are setup...
|
# This needs to occur before VirtualHost objects are setup...
|
||||||
# because this will change the underlying configuration and potential
|
# because this will change the underlying configuration and potential
|
||||||
# vhosts
|
# vhosts
|
||||||
self.reverter = reverter.Reverter(config)
|
self.reverter = reverter.Reverter(self.config)
|
||||||
self.reverter.recovery_routine()
|
self.reverter.recovery_routine()
|
||||||
|
|
||||||
def check_parsing_errors(self, lens):
|
def check_parsing_errors(self, lens):
|
||||||
|
|||||||
+28
-37
@@ -171,48 +171,49 @@ def config_changes(args, config):
|
|||||||
client.config_changes(config)
|
client.config_changes(config)
|
||||||
|
|
||||||
|
|
||||||
def _print_plugins(filtered, plugins, names):
|
def _print_plugins(plugins):
|
||||||
if not filtered:
|
# TODO: this functions should use IDisplay rather than printing
|
||||||
|
|
||||||
|
if not plugins:
|
||||||
print "No plugins found"
|
print "No plugins found"
|
||||||
|
|
||||||
for plugin_cls, content in filtered.iteritems():
|
for plugin_ep in plugins.itervalues():
|
||||||
print "* {0}".format(names[plugin_cls])
|
print "* {0}".format(plugin_ep.name)
|
||||||
print "Description: {0}".format(plugin_cls.description)
|
print "Description: {0}".format(plugin_ep.plugin_cls.description)
|
||||||
print "Interfaces: {0}".format(", ".join(
|
print "Interfaces: {0}".format(", ".join(
|
||||||
iface.__name__ for iface in zope.interface.implementedBy(
|
iface.__name__ for iface in zope.interface.implementedBy(
|
||||||
plugin_cls)))
|
plugin_ep.plugin_cls)))
|
||||||
print "Entry points:"
|
print "Entry point: {0}".format(plugin_ep.entry_point)
|
||||||
for entry_point in plugins[plugin_cls]:
|
|
||||||
print "- {0.dist}: {0}".format(entry_point)
|
if plugin_ep.initialized:
|
||||||
|
print "Initialized: {0}".format(plugin_ep.init())
|
||||||
|
|
||||||
# if filtered == prepared:
|
# if filtered == prepared:
|
||||||
if isinstance(content, tuple) and content[1] is not None:
|
#if isinstance(content, tuple) and content[1] is not None:
|
||||||
print content[1] # error
|
# print content[1] # error
|
||||||
print
|
|
||||||
|
print # whitespace between plugins
|
||||||
|
|
||||||
|
|
||||||
def plugins(args, config):
|
def plugins(args, config):
|
||||||
"""List plugins."""
|
"""List plugins."""
|
||||||
plugins = plugins_disco.find_plugins()
|
plugins = plugins_disco.PluginRegistry.find_all()
|
||||||
logging.debug("Discovered plugins: %s", plugins)
|
logging.debug("Discovered plugins: %s", plugins)
|
||||||
|
|
||||||
names = plugins_disco.name_plugins(plugins)
|
|
||||||
|
|
||||||
ifaces = [] if args.ifaces is None else args.ifaces
|
ifaces = [] if args.ifaces is None else args.ifaces
|
||||||
filtered = plugins_disco.filter_plugins(
|
filtered = plugins.filter(*((iface,) for iface in ifaces))
|
||||||
plugins, *((iface,) for iface in ifaces))
|
|
||||||
logging.debug("Filtered plugins: %s", filtered)
|
logging.debug("Filtered plugins: %s", filtered)
|
||||||
|
|
||||||
if not args.init and not args.prepare:
|
if not args.init and not args.prepare:
|
||||||
return _print_plugins(filtered, plugins, names)
|
return _print_plugins(filtered)
|
||||||
|
|
||||||
initialized = dict((plugin_cls, plugin_cls(config))
|
for plugin_ep in filtered.itervalues():
|
||||||
for plugin_cls in filtered)
|
plugin_ep.init(config)
|
||||||
verified = plugins_disco.verify_plugins(initialized, ifaces)
|
#verified = plugins_disco.verify_plugins(initialized, ifaces)
|
||||||
logging.debug("Verified plugins: %s", initialized)
|
#logging.debug("Verified plugins: %s", initialized)
|
||||||
|
|
||||||
if not args.prepare:
|
if not args.prepare:
|
||||||
return _print_plugins(initialized, plugins, names)
|
return _print_plugins(filtered)
|
||||||
|
|
||||||
prepared = plugins_disco.prepare_plugins(initialized)
|
prepared = plugins_disco.prepare_plugins(initialized)
|
||||||
logging.debug("Prepared plugins: %s", plugins)
|
logging.debug("Prepared plugins: %s", plugins)
|
||||||
@@ -327,12 +328,10 @@ def create_parser():
|
|||||||
paths_parser(parser.add_argument_group("paths"))
|
paths_parser(parser.add_argument_group("paths"))
|
||||||
|
|
||||||
# TODO: plugin_parser should be called for every detected plugin
|
# TODO: plugin_parser should be called for every detected plugin
|
||||||
plugin_parser(
|
for name, plugin_cls in [
|
||||||
parser.add_argument_group("apache"), prefix="apache",
|
("apache", apache_configurator.ApacheConfigurator),
|
||||||
plugin_cls=apache_configurator.ApacheConfigurator)
|
("nginx", nginx_configurator.NginxConfigurator)]:
|
||||||
plugin_parser(
|
plugin_cls.inject_parser_options(parser.add_argument_group(name), name)
|
||||||
parser.add_argument_group("nginx"), prefix="nginx",
|
|
||||||
plugin_cls=nginx_configurator.NginxConfigurator)
|
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@@ -360,14 +359,6 @@ def paths_parser(parser):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def plugin_parser(parser, prefix, plugin_cls):
|
|
||||||
def add(arg_name_no_prefix, *args, **kwargs):
|
|
||||||
parser.add_argument(
|
|
||||||
"--{0}-{1}".format(prefix, arg_name_no_prefix), *args, **kwargs)
|
|
||||||
plugin_cls.add_parser_arguments(add)
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=sys.argv[1:]):
|
def main(args=sys.argv[1:]):
|
||||||
"""Command line argument parsing and main script execution."""
|
"""Command line argument parsing and main script execution."""
|
||||||
# note: arg parser internally handles --help (and exits afterwards)
|
# note: arg parser internally handles --help (and exits afterwards)
|
||||||
|
|||||||
@@ -6,30 +6,63 @@ import zope.interface
|
|||||||
|
|
||||||
|
|
||||||
class IPluginFactory(zope.interface.Interface):
|
class IPluginFactory(zope.interface.Interface):
|
||||||
|
"""IPlugin factory.
|
||||||
|
|
||||||
def __call__(config):
|
Objects providing this interface will be called without satisfying
|
||||||
|
any entry point "extras" (extra dependencies) you might have defined
|
||||||
|
for your plugin, e.g (excerpt from ``setup.py`` script)::
|
||||||
|
|
||||||
|
setup(
|
||||||
|
...
|
||||||
|
entry_points={
|
||||||
|
'letsencrypt.plugins': [
|
||||||
|
'name=example_project.plugin[plugin_deps]',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
extras_require={
|
||||||
|
'plugin_deps': ['dep1', 'dep2'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Therefore, make sure such objects are importable and usable without
|
||||||
|
extras. This is necessary, because CLI does the following operations
|
||||||
|
(in order):
|
||||||
|
|
||||||
|
- loads an entry point,
|
||||||
|
- calls `inject_parser_options`,
|
||||||
|
- requires an entry point,
|
||||||
|
- creates plugin instance (`__call__`).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
description = zope.interface.Attribute("Short plugin description")
|
||||||
|
|
||||||
|
def __call__(config, name):
|
||||||
"""Create new `IPlugin`.
|
"""Create new `IPlugin`.
|
||||||
|
|
||||||
:param IConfig config: Configuration.
|
:param IConfig config: Configuration.
|
||||||
|
:param str name: Unique plugin name.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def add_parser_arguments(add):
|
def inject_parser_options(parser, name):
|
||||||
"""Add plugin arguments to the CLI argument parser.
|
"""Inject argument parser options (flags).
|
||||||
|
|
||||||
:param callable add: Function that proxies calls to
|
1. Be nice and prepend all options and destinations with
|
||||||
`argparse.ArgumentParser.add_argument` prepending options
|
`~.option_namespace` and `~.dest_namespace`.
|
||||||
with unique plugin name prefix.
|
|
||||||
|
2. Inject options (flags) only. Positional arguments are not
|
||||||
|
allowed, as this would break the CLI.
|
||||||
|
|
||||||
|
:param ArgumentParser parser: (Almost) top-level CLI parser.
|
||||||
|
:param str name: Unique plugin name.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO: move to IPlugin?
|
|
||||||
|
|
||||||
|
|
||||||
class IPlugin(zope.interface.Interface):
|
class IPlugin(zope.interface.Interface):
|
||||||
"""Let's Encrypt plugin."""
|
"""Let's Encrypt plugin."""
|
||||||
|
|
||||||
description = zope.interface.Attribute("Short plugin description")
|
|
||||||
|
|
||||||
def prepare():
|
def prepare():
|
||||||
"""Prepare the plugin.
|
"""Prepare the plugin.
|
||||||
|
|
||||||
|
|||||||
@@ -97,14 +97,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
add("init-script", default=constants.DEFAULT_INIT_SCRIPT,
|
add("init-script", default=constants.DEFAULT_INIT_SCRIPT,
|
||||||
help="Path to the Apache init script (used for server reload/restart).")
|
help="Path to the Apache init script (used for server reload/restart).")
|
||||||
|
|
||||||
def __init__(self, config, version=None):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize an Apache Configurator.
|
"""Initialize an Apache Configurator.
|
||||||
|
|
||||||
:param tup version: version of Apache as a tuple (2, 4, 7)
|
:param tup version: version of Apache as a tuple (2, 4, 7)
|
||||||
(used mostly for unittesting)
|
(used mostly for unittesting)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super(ApacheConfigurator, self).__init__(config)
|
version = kwargs.pop('version', None)
|
||||||
|
super(ApacheConfigurator, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Verify that all directories and files exist with proper permissions
|
# Verify that all directories and files exist with proper permissions
|
||||||
if os.geteuid() == 0:
|
if os.geteuid() == 0:
|
||||||
@@ -124,8 +125,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
def prepare(self):
|
def prepare(self):
|
||||||
"""Prepare the authenticator/installer."""
|
"""Prepare the authenticator/installer."""
|
||||||
self.parser = parser.ApacheParser(
|
self.parser = parser.ApacheParser(
|
||||||
self.aug, self.config.apache_server_root,
|
self.aug, self.conf('server-root'), self.conf('mod-ssl-conf'))
|
||||||
self.config.apache_mod_ssl_conf)
|
|
||||||
# Check for errors in parsing files with Augeas
|
# Check for errors in parsing files with Augeas
|
||||||
self.check_parsing_errors("httpd.aug")
|
self.check_parsing_errors("httpd.aug")
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
# on initialization
|
# on initialization
|
||||||
self._prepare_server_https()
|
self._prepare_server_https()
|
||||||
|
|
||||||
temp_install(self.config.apache_mod_ssl_conf)
|
temp_install(self.conf('mod-ssl-conf'))
|
||||||
|
|
||||||
def deploy_cert(self, domain, cert, key, cert_chain=None):
|
def deploy_cert(self, domain, cert, key, cert_chain=None):
|
||||||
"""Deploys certificate to specified virtual host.
|
"""Deploys certificate to specified virtual host.
|
||||||
@@ -401,10 +401,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
is appropriately listening on port 443.
|
is appropriately listening on port 443.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not mod_loaded("ssl_module", self.config.apache_ctl):
|
if not mod_loaded("ssl_module", self.conf('ctl')):
|
||||||
logging.info("Loading mod_ssl into Apache Server")
|
logging.info("Loading mod_ssl into Apache Server")
|
||||||
enable_mod("ssl", self.config.apache_init_script,
|
enable_mod("ssl", self.conf('init-script'),
|
||||||
self.config.apache_enmod)
|
self.conf('enmod'))
|
||||||
|
|
||||||
# Check for Listen 443
|
# Check for Listen 443
|
||||||
# Note: This could be made to also look for ip:443 combo
|
# Note: This could be made to also look for ip:443 combo
|
||||||
@@ -587,9 +587,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
:class:`~letsencrypt.client.plugins.apache.obj.VirtualHost`)
|
:class:`~letsencrypt.client.plugins.apache.obj.VirtualHost`)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not mod_loaded("rewrite_module", self.config.apache_ctl):
|
if not mod_loaded("rewrite_module", self.conf('ctl')):
|
||||||
enable_mod("rewrite", self.config.apache_init_script,
|
enable_mod("rewrite", self.conf('init-script'), self.conf('enmod'))
|
||||||
self.config.apache_enmod)
|
|
||||||
|
|
||||||
general_v = self._general_vhost(ssl_vhost)
|
general_v = self._general_vhost(ssl_vhost)
|
||||||
if general_v is None:
|
if general_v is None:
|
||||||
@@ -912,7 +911,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return apache_restart(self.config.apache_init_script)
|
return apache_restart(self.conf('init-script'))
|
||||||
|
|
||||||
def config_test(self): # pylint: disable=no-self-use
|
def config_test(self): # pylint: disable=no-self-use
|
||||||
"""Check the configuration of Apache for errors.
|
"""Check the configuration of Apache for errors.
|
||||||
@@ -923,7 +922,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["sudo", self.config.apache_ctl, "configtest"], # TODO: sudo?
|
["sudo", self.conf('ctl'), "configtest"], # TODO: sudo?
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
stdout, stderr = proc.communicate()
|
stdout, stderr = proc.communicate()
|
||||||
@@ -970,13 +969,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[self.config.apache_ctl, "-v"],
|
[self.conf('ctl'), "-v"],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
text = proc.communicate()[0]
|
text = proc.communicate()[0]
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
raise errors.LetsEncryptConfiguratorError(
|
raise errors.LetsEncryptConfiguratorError(
|
||||||
"Unable to run %s -v" % self.config.apache_ctl)
|
"Unable to run %s -v" % self.conf('ctl'))
|
||||||
|
|
||||||
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
|
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
|
||||||
matches = regex.findall(text)
|
matches = regex.findall(text)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ def get_apache_configurator(
|
|||||||
# This just states that the ssl module is already loaded
|
# This just states that the ssl module is already loaded
|
||||||
mock_popen().communicate.return_value = ("ssl_module", "")
|
mock_popen().communicate.return_value = ("ssl_module", "")
|
||||||
config = configurator.ApacheConfigurator(
|
config = configurator.ApacheConfigurator(
|
||||||
mock.MagicMock(
|
config=mock.MagicMock(
|
||||||
apache_server_root=config_path,
|
apache_server_root=config_path,
|
||||||
apache_mod_ssl_conf=ssl_options,
|
apache_mod_ssl_conf=ssl_options,
|
||||||
le_vhost_ext="-le-ssl.conf",
|
le_vhost_ext="-le-ssl.conf",
|
||||||
@@ -73,7 +73,8 @@ def get_apache_configurator(
|
|||||||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
|
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
|
||||||
work_dir=work_dir),
|
work_dir=work_dir),
|
||||||
version)
|
name="apache",
|
||||||
|
version=version)
|
||||||
|
|
||||||
config.prepare()
|
config.prepare()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"""Plugin common functions."""
|
||||||
|
import zope.interface
|
||||||
|
|
||||||
|
from letsencrypt.acme.jose import util as jose_util
|
||||||
|
|
||||||
|
from letsencrypt.client import interfaces
|
||||||
|
|
||||||
|
|
||||||
|
def option_namespace(name):
|
||||||
|
"""ArgumentParser options namespace (prefix of all options)."""
|
||||||
|
return name + '-'
|
||||||
|
|
||||||
|
def dest_namespace(name):
|
||||||
|
"""ArgumentParser dest namespace (prefix of all destinations)."""
|
||||||
|
return name + '_'
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin(object):
|
||||||
|
"""Generic plugin."""
|
||||||
|
zope.interface.implements(interfaces.IPlugin)
|
||||||
|
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||||
|
|
||||||
|
def __init__(self, config, name):
|
||||||
|
self.config = config
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def option_namespace(self):
|
||||||
|
return option_namespace(self.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dest_namespace(self):
|
||||||
|
return dest_namespace(self.name)
|
||||||
|
|
||||||
|
def dest(self, var):
|
||||||
|
"""Find a destination for given variable ``var``."""
|
||||||
|
# this should do exactly the same what ArgumentParser(arg),
|
||||||
|
# does to "arg" to compute "dest"
|
||||||
|
return self.dest_namespace + var.replace('-', '_')
|
||||||
|
|
||||||
|
def conf(self, var):
|
||||||
|
"""Find a configuration value for variable ``var``."""
|
||||||
|
return getattr(self.config, self.dest(var))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def inject_parser_options(cls, parser, name):
|
||||||
|
# dummy function, doesn't check if dest.startswith(self.dest_namespace)
|
||||||
|
def add(arg_name_no_prefix, *args, **kwargs):
|
||||||
|
return parser.add_argument(
|
||||||
|
"--{0}{1}".format(option_namespace(name), arg_name_no_prefix),
|
||||||
|
*args, **kwargs)
|
||||||
|
cls.add_parser_arguments(add)
|
||||||
|
|
||||||
|
@jose_util.abstractclassmethod
|
||||||
|
def add_parser_arguments(cls, add):
|
||||||
|
"""Add plugin arguments to the CLI argument parser.
|
||||||
|
|
||||||
|
:param callable add: Function that proxies calls to
|
||||||
|
`argparse.ArgumentParser.add_argument` prepending options
|
||||||
|
with unique plugin name prefix.
|
||||||
|
|
||||||
|
"""
|
||||||
@@ -12,52 +12,97 @@ from letsencrypt.client import interfaces
|
|||||||
from letsencrypt.client.display import ops as display_ops
|
from letsencrypt.client.display import ops as display_ops
|
||||||
|
|
||||||
|
|
||||||
def name_plugins(plugins):
|
class PluginEntryPoint(object):
|
||||||
# TODO: actually make it unambiguous...
|
"""Plugin entry point."""
|
||||||
names = {}
|
|
||||||
for plugin_cls, entry_points in plugins.iteritems():
|
PREFIX_FREE_DISTRIBUTIONS = ['letsencrypt']
|
||||||
entry_point = next(iter(entry_points)) # entry_points.peek()
|
"""Distributions for which prefix will be omitted."""
|
||||||
names[plugin_cls] = entry_point.name
|
|
||||||
return names
|
def __init__(self, entry_point):
|
||||||
|
self.name = self.entry_point_to_plugin_name(entry_point)
|
||||||
|
self.plugin_cls = entry_point.load()
|
||||||
|
self.entry_point = entry_point
|
||||||
|
self._initialized = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def initialized(self):
|
||||||
|
return self._initialized is not None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def entry_point_to_plugin_name(cls, entry_point):
|
||||||
|
if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS:
|
||||||
|
return entry_point.name
|
||||||
|
return entry_point.dist.key + ':' + entry_point.name
|
||||||
|
|
||||||
|
def init(self, config=None):
|
||||||
|
"""Memoized plugin inititialization."""
|
||||||
|
if not self.initialized:
|
||||||
|
self.entry_point.require() # fetch extras!
|
||||||
|
self._initialized = self.plugin_cls(config, self.name)
|
||||||
|
return self._initialized
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'PluginEntryPoint#{0}'.format(self.name)
|
||||||
|
|
||||||
|
|
||||||
def find_plugins():
|
class PluginRegistry(collections.Mapping):
|
||||||
|
"""Plugin registry."""
|
||||||
|
|
||||||
|
def __init__(self, plugins):
|
||||||
|
self.plugins = plugins
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_all(cls):
|
||||||
"""Find plugins using setuptools entry points."""
|
"""Find plugins using setuptools entry points."""
|
||||||
plugins = collections.defaultdict(set)
|
plugins = {}
|
||||||
for entry_point in pkg_resources.iter_entry_points(
|
for entry_point in pkg_resources.iter_entry_points(
|
||||||
constants.SETUPTOOLS_PLUGINS_ENTRY_POINT):
|
constants.SETUPTOOLS_PLUGINS_ENTRY_POINT):
|
||||||
plugin_cls = entry_point.load()
|
plugin_ep = PluginEntryPoint(entry_point)
|
||||||
plugins[plugin_cls].add(entry_point)
|
assert plugin_ep.name not in plugins, (
|
||||||
return plugins
|
'PREFIX_FREE_DISTRIBTIONS messed up')
|
||||||
|
plugins[plugin_ep.name] = plugin_ep
|
||||||
|
return cls(plugins)
|
||||||
|
|
||||||
|
def filter(self, *ifaces_groups):
|
||||||
def filter_plugins(plugins, *ifaces_groups):
|
|
||||||
"""Filter plugins based on interfaces."""
|
"""Filter plugins based on interfaces."""
|
||||||
return dict(
|
return type(self)(dict(
|
||||||
(plugin_cls, entry_points)
|
plugin_ep
|
||||||
for plugin_cls, entry_points in plugins.iteritems()
|
for plugin_ep in self.plugins.iteritems()
|
||||||
if not ifaces_groups or any(
|
if not ifaces_groups or any(
|
||||||
all(iface.implementedBy(plugin_cls) for iface in ifaces)
|
all(iface.implementedBy(plugin_ep.plugin_cls)
|
||||||
for ifaces in ifaces_groups))
|
for iface in ifaces)
|
||||||
|
for ifaces in ifaces_groups)))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '{0}({1!r})'.format(self.__class__.__name__, self.plugins)
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
return self.plugins[name]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.plugins)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.plugins)
|
||||||
|
|
||||||
|
|
||||||
def verify_plugins(initialized, ifaces):
|
def verify_plugins(initialized, ifaces):
|
||||||
"""Verify plugin objects."""
|
"""Verify plugin objects."""
|
||||||
verified = {}
|
verified = {}
|
||||||
for plugin_cls, plugin in initialized.iteritems():
|
for name, plugin_ep in initialized.iteritems():
|
||||||
verifies = True
|
verifies = True
|
||||||
for iface in ifaces: # zope.interface.providedBy(plugin)
|
for iface in ifaces: # zope.interface.providedBy(plugin)
|
||||||
try:
|
try:
|
||||||
zope.interface.verify.verifyObject(iface, plugin)
|
zope.interface.verify.verifyObject(iface, plugin_ep.init())
|
||||||
except zope.interface.exceptions.BrokenImplementation:
|
except zope.interface.exceptions.BrokenImplementation:
|
||||||
if iface.implementedBy(plugin_cls):
|
if iface.implementedBy(plugin_ep.plugin_cls):
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"%s implements %s but object does "
|
"%s implements %s but object does "
|
||||||
"not verify", plugin_cls, iface.__name__)
|
"not verify", plugin_ep.plugin_cls, iface.__name__)
|
||||||
verifies = False
|
verifies = False
|
||||||
break
|
break
|
||||||
if verifies:
|
if verifies:
|
||||||
verified[plugin_cls] = plugin
|
verified[name] = plugin_ep
|
||||||
return verified
|
return verified
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ from letsencrypt.client import interfaces
|
|||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
from letsencrypt.client import reverter
|
from letsencrypt.client import reverter
|
||||||
|
|
||||||
|
from letsencrypt.client.plugins import common
|
||||||
|
|
||||||
from letsencrypt.client.plugins.nginx import constants
|
from letsencrypt.client.plugins.nginx import constants
|
||||||
from letsencrypt.client.plugins.nginx import dvsni
|
from letsencrypt.client.plugins.nginx import dvsni
|
||||||
from letsencrypt.client.plugins.nginx import parser
|
from letsencrypt.client.plugins.nginx import parser
|
||||||
|
|
||||||
|
|
||||||
class NginxConfigurator(object):
|
class NginxConfigurator(common.Plugin):
|
||||||
# pylint: disable=too-many-instance-attributes,too-many-public-methods
|
# pylint: disable=too-many-instance-attributes,too-many-public-methods
|
||||||
"""Nginx configurator.
|
"""Nginx configurator.
|
||||||
|
|
||||||
@@ -60,14 +62,15 @@ class NginxConfigurator(object):
|
|||||||
"'nginx' binary, used for 'configtest' and retrieving nginx "
|
"'nginx' binary, used for 'configtest' and retrieving nginx "
|
||||||
"version number.")
|
"version number.")
|
||||||
|
|
||||||
def __init__(self, config, version=None):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize an Nginx Configurator.
|
"""Initialize an Nginx Configurator.
|
||||||
|
|
||||||
:param tup version: version of Nginx as a tuple (1, 4, 7)
|
:param tup version: version of Nginx as a tuple (1, 4, 7)
|
||||||
(used mostly for unittesting)
|
(used mostly for unittesting)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.config = config
|
version = kwargs.pop("version", None)
|
||||||
|
super(NginxConfigurator, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Verify that all directories and files exist with proper permissions
|
# Verify that all directories and files exist with proper permissions
|
||||||
if os.geteuid() == 0:
|
if os.geteuid() == 0:
|
||||||
@@ -85,21 +88,21 @@ class NginxConfigurator(object):
|
|||||||
self._enhance_func = {} # TODO: Support at least redirects
|
self._enhance_func = {} # TODO: Support at least redirects
|
||||||
|
|
||||||
# Set up reverter
|
# Set up reverter
|
||||||
self.reverter = reverter.Reverter(config)
|
self.reverter = reverter.Reverter(self.config)
|
||||||
self.reverter.recovery_routine()
|
self.reverter.recovery_routine()
|
||||||
|
|
||||||
# This is called in determine_authenticator and determine_installer
|
# This is called in determine_authenticator and determine_installer
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
"""Prepare the authenticator/installer."""
|
"""Prepare the authenticator/installer."""
|
||||||
self.parser = parser.NginxParser(
|
self.parser = parser.NginxParser(
|
||||||
self.config.nginx_server_root,
|
self.conf('server-root'),
|
||||||
self.config.nginx_mod_ssl_conf)
|
self.conf('mod-ssl-conf'))
|
||||||
|
|
||||||
# Set Version
|
# Set Version
|
||||||
if self.version is None:
|
if self.version is None:
|
||||||
self.version = self.get_version()
|
self.version = self.get_version()
|
||||||
|
|
||||||
temp_install(self.config.nginx_mod_ssl_conf)
|
temp_install(self.conf('mod-ssl-conf'))
|
||||||
|
|
||||||
# Entry point in main.py for installing cert
|
# Entry point in main.py for installing cert
|
||||||
def deploy_cert(self, domain, cert, key, cert_chain=None):
|
def deploy_cert(self, domain, cert, key, cert_chain=None):
|
||||||
@@ -323,7 +326,7 @@ class NginxConfigurator(object):
|
|||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return nginx_restart(self.config.nginx_ctl)
|
return nginx_restart(self.conf('ctl'))
|
||||||
|
|
||||||
def config_test(self): # pylint: disable=no-self-use
|
def config_test(self): # pylint: disable=no-self-use
|
||||||
"""Check the configuration of Nginx for errors.
|
"""Check the configuration of Nginx for errors.
|
||||||
@@ -334,7 +337,7 @@ class NginxConfigurator(object):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[self.config.nginx_ctl, "-t"],
|
[self.conf('ctl'), "-t"],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
stdout, stderr = proc.communicate()
|
stdout, stderr = proc.communicate()
|
||||||
@@ -381,13 +384,13 @@ class NginxConfigurator(object):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[self.config.nginx_ctl, "-V"],
|
[self.conf('ctl'), "-V"],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
text = proc.communicate()[1] # nginx prints output to stderr
|
text = proc.communicate()[1] # nginx prints output to stderr
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
raise errors.LetsEncryptConfiguratorError(
|
raise errors.LetsEncryptConfiguratorError(
|
||||||
"Unable to run %s -V" % self.config.nginx_ctl)
|
"Unable to run %s -V" % self.conf('ctl'))
|
||||||
|
|
||||||
version_regex = re.compile(r"nginx/([0-9\.]*)", re.IGNORECASE)
|
version_regex = re.compile(r"nginx/([0-9\.]*)", re.IGNORECASE)
|
||||||
version_matches = version_regex.findall(text)
|
version_matches = version_regex.findall(text)
|
||||||
|
|||||||
@@ -65,12 +65,13 @@ def get_nginx_configurator(
|
|||||||
backups = os.path.join(work_dir, "backups")
|
backups = os.path.join(work_dir, "backups")
|
||||||
|
|
||||||
config = configurator.NginxConfigurator(
|
config = configurator.NginxConfigurator(
|
||||||
mock.MagicMock(
|
config=mock.MagicMock(
|
||||||
nginx_server_root=config_path, nginx_mod_ssl_conf=ssl_options,
|
nginx_server_root=config_path, nginx_mod_ssl_conf=ssl_options,
|
||||||
le_vhost_ext="-le-ssl.conf", backup_dir=backups,
|
le_vhost_ext="-le-ssl.conf", backup_dir=backups,
|
||||||
config_dir=config_dir, work_dir=work_dir,
|
config_dir=config_dir, work_dir=work_dir,
|
||||||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS")),
|
in_progress_dir=os.path.join(backups, "IN_PROGRESS")),
|
||||||
version)
|
name="nginx",
|
||||||
|
version=version)
|
||||||
config.prepare()
|
config.prepare()
|
||||||
return config
|
return config
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ from letsencrypt.acme import challenges
|
|||||||
from letsencrypt.client import achallenges
|
from letsencrypt.client import achallenges
|
||||||
from letsencrypt.client import interfaces
|
from letsencrypt.client import interfaces
|
||||||
|
|
||||||
|
from letsencrypt.client.plugins import common
|
||||||
|
|
||||||
class StandaloneAuthenticator(object):
|
|
||||||
|
class StandaloneAuthenticator(common.Plugin):
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
"""Standalone authenticator.
|
"""Standalone authenticator.
|
||||||
|
|
||||||
@@ -29,15 +31,10 @@ class StandaloneAuthenticator(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
zope.interface.implements(interfaces.IAuthenticator)
|
zope.interface.implements(interfaces.IAuthenticator)
|
||||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
|
||||||
|
|
||||||
description = "Standalone Authenticator"
|
description = "Standalone Authenticator"
|
||||||
|
|
||||||
@classmethod
|
def __init__(self, *args, **kwargs):
|
||||||
def add_parser_arguments(cls, add):
|
super(StandaloneAuthenticator, self).__init__(*args, **kwargs)
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, unused_config):
|
|
||||||
self.child_pid = None
|
self.child_pid = None
|
||||||
self.parent_pid = os.getpid()
|
self.parent_pid = os.getpid()
|
||||||
self.subproc_state = None
|
self.subproc_state = None
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class ChallPrefTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
|
|
||||||
def test_chall_pref(self):
|
def test_chall_pref(self):
|
||||||
self.assertEqual(self.authenticator.get_chall_pref("example.com"),
|
self.assertEqual(self.authenticator.get_chall_pref("example.com"),
|
||||||
@@ -65,7 +65,7 @@ class SNICallbackTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
test_key = pkg_resources.resource_string(
|
test_key = pkg_resources.resource_string(
|
||||||
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
||||||
key = le_util.Key("foo", test_key)
|
key = le_util.Key("foo", test_key)
|
||||||
@@ -108,7 +108,7 @@ class ClientSignalHandlerTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
self.authenticator.tasks = {"foononce.acme.invalid": "stuff"}
|
self.authenticator.tasks = {"foononce.acme.invalid": "stuff"}
|
||||||
self.authenticator.child_pid = 12345
|
self.authenticator.child_pid = 12345
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ class SubprocSignalHandlerTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
self.authenticator.tasks = {"foononce.acme.invalid": "stuff"}
|
self.authenticator.tasks = {"foononce.acme.invalid": "stuff"}
|
||||||
self.authenticator.child_pid = 12345
|
self.authenticator.child_pid = 12345
|
||||||
self.authenticator.parent_pid = 23456
|
self.authenticator.parent_pid = 23456
|
||||||
@@ -189,7 +189,7 @@ class AlreadyListeningTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
|
|
||||||
@mock.patch("letsencrypt.client.plugins.standalone.authenticator.psutil."
|
@mock.patch("letsencrypt.client.plugins.standalone.authenticator.psutil."
|
||||||
"net_connections")
|
"net_connections")
|
||||||
@@ -296,7 +296,7 @@ class PerformTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
|
|
||||||
test_key = pkg_resources.resource_string(
|
test_key = pkg_resources.resource_string(
|
||||||
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
||||||
@@ -375,7 +375,7 @@ class StartListenerTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
|
|
||||||
@mock.patch("letsencrypt.client.plugins.standalone.authenticator."
|
@mock.patch("letsencrypt.client.plugins.standalone.authenticator."
|
||||||
"Crypto.Random.atfork")
|
"Crypto.Random.atfork")
|
||||||
@@ -410,7 +410,7 @@ class DoParentProcessTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
|
|
||||||
@mock.patch("letsencrypt.client.plugins.standalone.authenticator."
|
@mock.patch("letsencrypt.client.plugins.standalone.authenticator."
|
||||||
"signal.signal")
|
"signal.signal")
|
||||||
@@ -464,7 +464,7 @@ class DoChildProcessTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
test_key = pkg_resources.resource_string(
|
test_key = pkg_resources.resource_string(
|
||||||
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
||||||
key = le_util.Key("foo", test_key)
|
key = le_util.Key("foo", test_key)
|
||||||
@@ -562,7 +562,7 @@ class CleanupTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||||
StandaloneAuthenticator
|
StandaloneAuthenticator
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
self.achall = achallenges.DVSNI(
|
self.achall = achallenges.DVSNI(
|
||||||
challb=acme_util.chall_to_challb(
|
challb=acme_util.chall_to_challb(
|
||||||
challenges.DVSNI(r="whee", nonce="foononce"), "pending"),
|
challenges.DVSNI(r="whee", nonce="foononce"), "pending"),
|
||||||
@@ -595,7 +595,7 @@ class MoreInfoTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import (
|
from letsencrypt.client.plugins.standalone.authenticator import (
|
||||||
StandaloneAuthenticator)
|
StandaloneAuthenticator)
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
|
|
||||||
def test_more_info(self):
|
def test_more_info(self):
|
||||||
"""Make sure exceptions aren't raised."""
|
"""Make sure exceptions aren't raised."""
|
||||||
@@ -607,7 +607,7 @@ class InitTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.plugins.standalone.authenticator import (
|
from letsencrypt.client.plugins.standalone.authenticator import (
|
||||||
StandaloneAuthenticator)
|
StandaloneAuthenticator)
|
||||||
self.authenticator = StandaloneAuthenticator(None)
|
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||||
|
|
||||||
def test_prepare(self):
|
def test_prepare(self):
|
||||||
"""Make sure exceptions aren't raised.
|
"""Make sure exceptions aren't raised.
|
||||||
|
|||||||
Reference in New Issue
Block a user