From 19cff0083556288fb64b4dbb38d2f3a7949d43e4 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 07:01:44 +0000 Subject: [PATCH] common.Plugin (with .conf(var), PluginEntryPoint, PluginRegistry --- .../plugins/letsencrypt_example_plugins.py | 21 ++-- examples/plugins/setup.py | 3 +- letsencrypt/client/augeas_configurator.py | 9 +- letsencrypt/client/cli.py | 65 +++++------ letsencrypt/client/interfaces.py | 51 +++++++-- .../client/plugins/apache/configurator.py | 29 +++-- .../client/plugins/apache/tests/util.py | 5 +- letsencrypt/client/plugins/common.py | 62 +++++++++++ letsencrypt/client/plugins/disco.py | 101 +++++++++++++----- .../client/plugins/nginx/configurator.py | 25 +++-- .../client/plugins/nginx/tests/util.py | 5 +- .../plugins/standalone/authenticator.py | 13 +-- .../standalone/tests/authenticator_test.py | 24 ++--- 13 files changed, 278 insertions(+), 135 deletions(-) create mode 100644 letsencrypt/client/plugins/common.py diff --git a/examples/plugins/letsencrypt_example_plugins.py b/examples/plugins/letsencrypt_example_plugins.py index 987a2b33b..11baf35a7 100644 --- a/examples/plugins/letsencrypt_example_plugins.py +++ b/examples/plugins/letsencrypt_example_plugins.py @@ -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)... diff --git a/examples/plugins/setup.py b/examples/plugins/setup.py index 599d57020..71bb95333 100644 --- a/examples/plugins/setup.py +++ b/examples/plugins/setup.py @@ -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', ], }, ) diff --git a/letsencrypt/client/augeas_configurator.py b/letsencrypt/client/augeas_configurator.py index 8854fef09..713d49291 100644 --- a/letsencrypt/client/augeas_configurator.py +++ b/letsencrypt/client/augeas_configurator.py @@ -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): diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 3a8faf481..148562e4c 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -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) diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index 85bfa7256..df8b616a6 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -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. diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index 26e81e641..d014e6c2e 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -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) diff --git a/letsencrypt/client/plugins/apache/tests/util.py b/letsencrypt/client/plugins/apache/tests/util.py index feeb9490e..95c7928dd 100644 --- a/letsencrypt/client/plugins/apache/tests/util.py +++ b/letsencrypt/client/plugins/apache/tests/util.py @@ -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() diff --git a/letsencrypt/client/plugins/common.py b/letsencrypt/client/plugins/common.py new file mode 100644 index 000000000..ba6efeccc --- /dev/null +++ b/letsencrypt/client/plugins/common.py @@ -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. + + """ diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index d2526d583..350929641 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -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 diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 4a0ae9657..edeb4adb0 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -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) diff --git a/letsencrypt/client/plugins/nginx/tests/util.py b/letsencrypt/client/plugins/nginx/tests/util.py index 8acfa8ff6..dc112af16 100644 --- a/letsencrypt/client/plugins/nginx/tests/util.py +++ b/letsencrypt/client/plugins/nginx/tests/util.py @@ -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 diff --git a/letsencrypt/client/plugins/standalone/authenticator.py b/letsencrypt/client/plugins/standalone/authenticator.py index aaefd09da..668b3f716 100644 --- a/letsencrypt/client/plugins/standalone/authenticator.py +++ b/letsencrypt/client/plugins/standalone/authenticator.py @@ -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 diff --git a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py b/letsencrypt/client/plugins/standalone/tests/authenticator_test.py index 23cd43bd5..e13452ccc 100644 --- a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py +++ b/letsencrypt/client/plugins/standalone/tests/authenticator_test.py @@ -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.