mirror of
https://github.com/certbot/certbot.git
synced 2026-07-26 07:39:52 +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
|
||||
|
||||
from letsencrypt.client import interfaces
|
||||
from letsencrypt.client.plugins import common
|
||||
|
||||
|
||||
class Authenticator(object):
|
||||
class Authenticator(common.Plugin):
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
|
||||
description = 'Example Authenticator plugin'
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
# Implement all methods from IAuthenticator, remembering to add
|
||||
# "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={
|
||||
'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
|
||||
|
||||
from letsencrypt.client import reverter
|
||||
from letsencrypt.client.plugins import common
|
||||
|
||||
|
||||
class AugeasConfigurator(object):
|
||||
class AugeasConfigurator(common.Plugin):
|
||||
"""Base Augeas Configurator class.
|
||||
|
||||
:ivar config: Configuration.
|
||||
@@ -21,8 +22,8 @@ class AugeasConfigurator(object):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AugeasConfigurator, self).__init__(*args, **kwargs)
|
||||
|
||||
# Set Augeas flags to not save backup (we do it ourselves)
|
||||
# Set Augeas to not load anything by default
|
||||
@@ -34,7 +35,7 @@ class AugeasConfigurator(object):
|
||||
# This needs to occur before VirtualHost objects are setup...
|
||||
# because this will change the underlying configuration and potential
|
||||
# vhosts
|
||||
self.reverter = reverter.Reverter(config)
|
||||
self.reverter = reverter.Reverter(self.config)
|
||||
self.reverter.recovery_routine()
|
||||
|
||||
def check_parsing_errors(self, lens):
|
||||
|
||||
+28
-37
@@ -171,48 +171,49 @@ def config_changes(args, config):
|
||||
client.config_changes(config)
|
||||
|
||||
|
||||
def _print_plugins(filtered, plugins, names):
|
||||
if not filtered:
|
||||
def _print_plugins(plugins):
|
||||
# TODO: this functions should use IDisplay rather than printing
|
||||
|
||||
if not plugins:
|
||||
print "No plugins found"
|
||||
|
||||
for plugin_cls, content in filtered.iteritems():
|
||||
print "* {0}".format(names[plugin_cls])
|
||||
print "Description: {0}".format(plugin_cls.description)
|
||||
for plugin_ep in plugins.itervalues():
|
||||
print "* {0}".format(plugin_ep.name)
|
||||
print "Description: {0}".format(plugin_ep.plugin_cls.description)
|
||||
print "Interfaces: {0}".format(", ".join(
|
||||
iface.__name__ for iface in zope.interface.implementedBy(
|
||||
plugin_cls)))
|
||||
print "Entry points:"
|
||||
for entry_point in plugins[plugin_cls]:
|
||||
print "- {0.dist}: {0}".format(entry_point)
|
||||
plugin_ep.plugin_cls)))
|
||||
print "Entry point: {0}".format(plugin_ep.entry_point)
|
||||
|
||||
if plugin_ep.initialized:
|
||||
print "Initialized: {0}".format(plugin_ep.init())
|
||||
|
||||
# if filtered == prepared:
|
||||
if isinstance(content, tuple) and content[1] is not None:
|
||||
print content[1] # error
|
||||
print
|
||||
#if isinstance(content, tuple) and content[1] is not None:
|
||||
# print content[1] # error
|
||||
|
||||
print # whitespace between plugins
|
||||
|
||||
|
||||
def plugins(args, config):
|
||||
"""List plugins."""
|
||||
plugins = plugins_disco.find_plugins()
|
||||
plugins = plugins_disco.PluginRegistry.find_all()
|
||||
logging.debug("Discovered plugins: %s", plugins)
|
||||
|
||||
names = plugins_disco.name_plugins(plugins)
|
||||
|
||||
ifaces = [] if args.ifaces is None else args.ifaces
|
||||
filtered = plugins_disco.filter_plugins(
|
||||
plugins, *((iface,) for iface in ifaces))
|
||||
filtered = plugins.filter(*((iface,) for iface in ifaces))
|
||||
logging.debug("Filtered plugins: %s", filtered)
|
||||
|
||||
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_cls in filtered)
|
||||
verified = plugins_disco.verify_plugins(initialized, ifaces)
|
||||
logging.debug("Verified plugins: %s", initialized)
|
||||
for plugin_ep in filtered.itervalues():
|
||||
plugin_ep.init(config)
|
||||
#verified = plugins_disco.verify_plugins(initialized, ifaces)
|
||||
#logging.debug("Verified plugins: %s", initialized)
|
||||
|
||||
if not args.prepare:
|
||||
return _print_plugins(initialized, plugins, names)
|
||||
return _print_plugins(filtered)
|
||||
|
||||
prepared = plugins_disco.prepare_plugins(initialized)
|
||||
logging.debug("Prepared plugins: %s", plugins)
|
||||
@@ -327,12 +328,10 @@ def create_parser():
|
||||
paths_parser(parser.add_argument_group("paths"))
|
||||
|
||||
# TODO: plugin_parser should be called for every detected plugin
|
||||
plugin_parser(
|
||||
parser.add_argument_group("apache"), prefix="apache",
|
||||
plugin_cls=apache_configurator.ApacheConfigurator)
|
||||
plugin_parser(
|
||||
parser.add_argument_group("nginx"), prefix="nginx",
|
||||
plugin_cls=nginx_configurator.NginxConfigurator)
|
||||
for name, plugin_cls in [
|
||||
("apache", apache_configurator.ApacheConfigurator),
|
||||
("nginx", nginx_configurator.NginxConfigurator)]:
|
||||
plugin_cls.inject_parser_options(parser.add_argument_group(name), name)
|
||||
|
||||
return parser
|
||||
|
||||
@@ -360,14 +359,6 @@ def paths_parser(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:]):
|
||||
"""Command line argument parsing and main script execution."""
|
||||
# note: arg parser internally handles --help (and exits afterwards)
|
||||
|
||||
@@ -6,30 +6,63 @@ import zope.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`.
|
||||
|
||||
:param IConfig config: Configuration.
|
||||
:param str name: Unique plugin name.
|
||||
|
||||
"""
|
||||
|
||||
def add_parser_arguments(add):
|
||||
"""Add plugin arguments to the CLI argument parser.
|
||||
def inject_parser_options(parser, name):
|
||||
"""Inject argument parser options (flags).
|
||||
|
||||
:param callable add: Function that proxies calls to
|
||||
`argparse.ArgumentParser.add_argument` prepending options
|
||||
with unique plugin name prefix.
|
||||
1. Be nice and prepend all options and destinations with
|
||||
`~.option_namespace` and `~.dest_namespace`.
|
||||
|
||||
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):
|
||||
"""Let's Encrypt plugin."""
|
||||
|
||||
description = zope.interface.Attribute("Short plugin description")
|
||||
|
||||
def prepare():
|
||||
"""Prepare the plugin.
|
||||
|
||||
|
||||
@@ -97,14 +97,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
add("init-script", default=constants.DEFAULT_INIT_SCRIPT,
|
||||
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.
|
||||
|
||||
:param tup version: version of Apache as a tuple (2, 4, 7)
|
||||
(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
|
||||
if os.geteuid() == 0:
|
||||
@@ -124,8 +125,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
def prepare(self):
|
||||
"""Prepare the authenticator/installer."""
|
||||
self.parser = parser.ApacheParser(
|
||||
self.aug, self.config.apache_server_root,
|
||||
self.config.apache_mod_ssl_conf)
|
||||
self.aug, self.conf('server-root'), self.conf('mod-ssl-conf'))
|
||||
# Check for errors in parsing files with Augeas
|
||||
self.check_parsing_errors("httpd.aug")
|
||||
|
||||
@@ -143,7 +143,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# on initialization
|
||||
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):
|
||||
"""Deploys certificate to specified virtual host.
|
||||
@@ -401,10 +401,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
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")
|
||||
enable_mod("ssl", self.config.apache_init_script,
|
||||
self.config.apache_enmod)
|
||||
enable_mod("ssl", self.conf('init-script'),
|
||||
self.conf('enmod'))
|
||||
|
||||
# Check for Listen 443
|
||||
# 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`)
|
||||
|
||||
"""
|
||||
if not mod_loaded("rewrite_module", self.config.apache_ctl):
|
||||
enable_mod("rewrite", self.config.apache_init_script,
|
||||
self.config.apache_enmod)
|
||||
if not mod_loaded("rewrite_module", self.conf('ctl')):
|
||||
enable_mod("rewrite", self.conf('init-script'), self.conf('enmod'))
|
||||
|
||||
general_v = self._general_vhost(ssl_vhost)
|
||||
if general_v is None:
|
||||
@@ -912,7 +911,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
: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
|
||||
"""Check the configuration of Apache for errors.
|
||||
@@ -923,7 +922,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
["sudo", self.config.apache_ctl, "configtest"], # TODO: sudo?
|
||||
["sudo", self.conf('ctl'), "configtest"], # TODO: sudo?
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
@@ -970,13 +969,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[self.config.apache_ctl, "-v"],
|
||||
[self.conf('ctl'), "-v"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
text = proc.communicate()[0]
|
||||
except (OSError, ValueError):
|
||||
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)
|
||||
matches = regex.findall(text)
|
||||
|
||||
@@ -64,7 +64,7 @@ def get_apache_configurator(
|
||||
# This just states that the ssl module is already loaded
|
||||
mock_popen().communicate.return_value = ("ssl_module", "")
|
||||
config = configurator.ApacheConfigurator(
|
||||
mock.MagicMock(
|
||||
config=mock.MagicMock(
|
||||
apache_server_root=config_path,
|
||||
apache_mod_ssl_conf=ssl_options,
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
@@ -73,7 +73,8 @@ def get_apache_configurator(
|
||||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
|
||||
work_dir=work_dir),
|
||||
version)
|
||||
name="apache",
|
||||
version=version)
|
||||
|
||||
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
|
||||
|
||||
|
||||
def name_plugins(plugins):
|
||||
# TODO: actually make it unambiguous...
|
||||
names = {}
|
||||
for plugin_cls, entry_points in plugins.iteritems():
|
||||
entry_point = next(iter(entry_points)) # entry_points.peek()
|
||||
names[plugin_cls] = entry_point.name
|
||||
return names
|
||||
class PluginEntryPoint(object):
|
||||
"""Plugin entry point."""
|
||||
|
||||
PREFIX_FREE_DISTRIBUTIONS = ['letsencrypt']
|
||||
"""Distributions for which prefix will be omitted."""
|
||||
|
||||
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():
|
||||
"""Find plugins using setuptools entry points."""
|
||||
plugins = collections.defaultdict(set)
|
||||
for entry_point in pkg_resources.iter_entry_points(
|
||||
constants.SETUPTOOLS_PLUGINS_ENTRY_POINT):
|
||||
plugin_cls = entry_point.load()
|
||||
plugins[plugin_cls].add(entry_point)
|
||||
return plugins
|
||||
class PluginRegistry(collections.Mapping):
|
||||
"""Plugin registry."""
|
||||
|
||||
def __init__(self, plugins):
|
||||
self.plugins = plugins
|
||||
|
||||
def filter_plugins(plugins, *ifaces_groups):
|
||||
"""Filter plugins based on interfaces."""
|
||||
return dict(
|
||||
(plugin_cls, entry_points)
|
||||
for plugin_cls, entry_points in plugins.iteritems()
|
||||
if not ifaces_groups or any(
|
||||
all(iface.implementedBy(plugin_cls) for iface in ifaces)
|
||||
for ifaces in ifaces_groups))
|
||||
@classmethod
|
||||
def find_all(cls):
|
||||
"""Find plugins using setuptools entry points."""
|
||||
plugins = {}
|
||||
for entry_point in pkg_resources.iter_entry_points(
|
||||
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
|
||||
return cls(plugins)
|
||||
|
||||
def filter(self, *ifaces_groups):
|
||||
"""Filter plugins based on interfaces."""
|
||||
return type(self)(dict(
|
||||
plugin_ep
|
||||
for plugin_ep in self.plugins.iteritems()
|
||||
if not ifaces_groups or any(
|
||||
all(iface.implementedBy(plugin_ep.plugin_cls)
|
||||
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):
|
||||
"""Verify plugin objects."""
|
||||
verified = {}
|
||||
for plugin_cls, plugin in initialized.iteritems():
|
||||
for name, plugin_ep in initialized.iteritems():
|
||||
verifies = True
|
||||
for iface in ifaces: # zope.interface.providedBy(plugin)
|
||||
try:
|
||||
zope.interface.verify.verifyObject(iface, plugin)
|
||||
zope.interface.verify.verifyObject(iface, plugin_ep.init())
|
||||
except zope.interface.exceptions.BrokenImplementation:
|
||||
if iface.implementedBy(plugin_cls):
|
||||
if iface.implementedBy(plugin_ep.plugin_cls):
|
||||
logging.debug(
|
||||
"%s implements %s but object does "
|
||||
"not verify", plugin_cls, iface.__name__)
|
||||
"not verify", plugin_ep.plugin_cls, iface.__name__)
|
||||
verifies = False
|
||||
break
|
||||
if verifies:
|
||||
verified[plugin_cls] = plugin
|
||||
verified[name] = plugin_ep
|
||||
return verified
|
||||
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@ from letsencrypt.client import interfaces
|
||||
from letsencrypt.client import le_util
|
||||
from letsencrypt.client import reverter
|
||||
|
||||
from letsencrypt.client.plugins import common
|
||||
|
||||
from letsencrypt.client.plugins.nginx import constants
|
||||
from letsencrypt.client.plugins.nginx import dvsni
|
||||
from letsencrypt.client.plugins.nginx import parser
|
||||
|
||||
|
||||
class NginxConfigurator(object):
|
||||
class NginxConfigurator(common.Plugin):
|
||||
# pylint: disable=too-many-instance-attributes,too-many-public-methods
|
||||
"""Nginx configurator.
|
||||
|
||||
@@ -60,14 +62,15 @@ class NginxConfigurator(object):
|
||||
"'nginx' binary, used for 'configtest' and retrieving nginx "
|
||||
"version number.")
|
||||
|
||||
def __init__(self, config, version=None):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize an Nginx Configurator.
|
||||
|
||||
:param tup version: version of Nginx as a tuple (1, 4, 7)
|
||||
(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
|
||||
if os.geteuid() == 0:
|
||||
@@ -85,21 +88,21 @@ class NginxConfigurator(object):
|
||||
self._enhance_func = {} # TODO: Support at least redirects
|
||||
|
||||
# Set up reverter
|
||||
self.reverter = reverter.Reverter(config)
|
||||
self.reverter = reverter.Reverter(self.config)
|
||||
self.reverter.recovery_routine()
|
||||
|
||||
# This is called in determine_authenticator and determine_installer
|
||||
def prepare(self):
|
||||
"""Prepare the authenticator/installer."""
|
||||
self.parser = parser.NginxParser(
|
||||
self.config.nginx_server_root,
|
||||
self.config.nginx_mod_ssl_conf)
|
||||
self.conf('server-root'),
|
||||
self.conf('mod-ssl-conf'))
|
||||
|
||||
# Set Version
|
||||
if self.version is None:
|
||||
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
|
||||
def deploy_cert(self, domain, cert, key, cert_chain=None):
|
||||
@@ -323,7 +326,7 @@ class NginxConfigurator(object):
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
return nginx_restart(self.config.nginx_ctl)
|
||||
return nginx_restart(self.conf('ctl'))
|
||||
|
||||
def config_test(self): # pylint: disable=no-self-use
|
||||
"""Check the configuration of Nginx for errors.
|
||||
@@ -334,7 +337,7 @@ class NginxConfigurator(object):
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[self.config.nginx_ctl, "-t"],
|
||||
[self.conf('ctl'), "-t"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
@@ -381,13 +384,13 @@ class NginxConfigurator(object):
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[self.config.nginx_ctl, "-V"],
|
||||
[self.conf('ctl'), "-V"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
text = proc.communicate()[1] # nginx prints output to stderr
|
||||
except (OSError, ValueError):
|
||||
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_matches = version_regex.findall(text)
|
||||
|
||||
@@ -65,12 +65,13 @@ def get_nginx_configurator(
|
||||
backups = os.path.join(work_dir, "backups")
|
||||
|
||||
config = configurator.NginxConfigurator(
|
||||
mock.MagicMock(
|
||||
config=mock.MagicMock(
|
||||
nginx_server_root=config_path, nginx_mod_ssl_conf=ssl_options,
|
||||
le_vhost_ext="-le-ssl.conf", backup_dir=backups,
|
||||
config_dir=config_dir, work_dir=work_dir,
|
||||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS")),
|
||||
version)
|
||||
name="nginx",
|
||||
version=version)
|
||||
config.prepare()
|
||||
return config
|
||||
|
||||
@@ -17,8 +17,10 @@ from letsencrypt.acme import challenges
|
||||
from letsencrypt.client import achallenges
|
||||
from letsencrypt.client import interfaces
|
||||
|
||||
from letsencrypt.client.plugins import common
|
||||
|
||||
class StandaloneAuthenticator(object):
|
||||
|
||||
class StandaloneAuthenticator(common.Plugin):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
"""Standalone authenticator.
|
||||
|
||||
@@ -29,15 +31,10 @@ class StandaloneAuthenticator(object):
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Standalone Authenticator"
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
pass
|
||||
|
||||
def __init__(self, unused_config):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(StandaloneAuthenticator, self).__init__(*args, **kwargs)
|
||||
self.child_pid = None
|
||||
self.parent_pid = os.getpid()
|
||||
self.subproc_state = None
|
||||
|
||||
@@ -53,7 +53,7 @@ class ChallPrefTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
|
||||
def test_chall_pref(self):
|
||||
self.assertEqual(self.authenticator.get_chall_pref("example.com"),
|
||||
@@ -65,7 +65,7 @@ class SNICallbackTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
test_key = pkg_resources.resource_string(
|
||||
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
||||
key = le_util.Key("foo", test_key)
|
||||
@@ -108,7 +108,7 @@ class ClientSignalHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
self.authenticator.tasks = {"foononce.acme.invalid": "stuff"}
|
||||
self.authenticator.child_pid = 12345
|
||||
|
||||
@@ -137,7 +137,7 @@ class SubprocSignalHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
self.authenticator.tasks = {"foononce.acme.invalid": "stuff"}
|
||||
self.authenticator.child_pid = 12345
|
||||
self.authenticator.parent_pid = 23456
|
||||
@@ -189,7 +189,7 @@ class AlreadyListeningTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
|
||||
@mock.patch("letsencrypt.client.plugins.standalone.authenticator.psutil."
|
||||
"net_connections")
|
||||
@@ -296,7 +296,7 @@ class PerformTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
|
||||
test_key = pkg_resources.resource_string(
|
||||
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
||||
@@ -375,7 +375,7 @@ class StartListenerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
|
||||
@mock.patch("letsencrypt.client.plugins.standalone.authenticator."
|
||||
"Crypto.Random.atfork")
|
||||
@@ -410,7 +410,7 @@ class DoParentProcessTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
|
||||
@mock.patch("letsencrypt.client.plugins.standalone.authenticator."
|
||||
"signal.signal")
|
||||
@@ -464,7 +464,7 @@ class DoChildProcessTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
test_key = pkg_resources.resource_string(
|
||||
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
|
||||
key = le_util.Key("foo", test_key)
|
||||
@@ -562,7 +562,7 @@ class CleanupTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import \
|
||||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
self.achall = achallenges.DVSNI(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.DVSNI(r="whee", nonce="foononce"), "pending"),
|
||||
@@ -595,7 +595,7 @@ class MoreInfoTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import (
|
||||
StandaloneAuthenticator)
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
|
||||
def test_more_info(self):
|
||||
"""Make sure exceptions aren't raised."""
|
||||
@@ -607,7 +607,7 @@ class InitTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.client.plugins.standalone.authenticator import (
|
||||
StandaloneAuthenticator)
|
||||
self.authenticator = StandaloneAuthenticator(None)
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
|
||||
def test_prepare(self):
|
||||
"""Make sure exceptions aren't raised.
|
||||
|
||||
Reference in New Issue
Block a user