common.Plugin (with .conf(var), PluginEntryPoint, PluginRegistry

This commit is contained in:
Jakub Warmuz
2015-05-02 07:01:44 +00:00
parent 17e8ddcb5c
commit 19cff00835
13 changed files with 278 additions and 135 deletions
@@ -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)...
+2 -1
View File
@@ -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',
], ],
}, },
) )
+5 -4
View File
@@ -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
View File
@@ -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)
+42 -9
View File
@@ -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()
+62
View File
@@ -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.
"""
+69 -24
View File
@@ -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.