mirror of
https://github.com/certbot/certbot.git
synced 2026-06-06 23:32:06 -04:00
Merge branch 'master' into snap-remotebuild
This commit is contained in:
commit
6b7dd05126
14 changed files with 153 additions and 67 deletions
|
|
@ -237,6 +237,7 @@ Authors
|
|||
* [Spencer Bliven](https://github.com/sbliven)
|
||||
* [Stacey Sheldon](https://github.com/solidgoldbomb)
|
||||
* [Stavros Korokithakis](https://github.com/skorokithakis)
|
||||
* [Ștefan Talpalaru](https://github.com/stefantalpalaru)
|
||||
* [Stefan Weil](https://github.com/stweil)
|
||||
* [Steve Desmond](https://github.com/stevedesmond-ca)
|
||||
* [sydneyli](https://github.com/sydneyli)
|
||||
|
|
|
|||
|
|
@ -279,16 +279,17 @@ def load_sample_data_path(workspace):
|
|||
shutil.copytree(original, copied, symlinks=True)
|
||||
|
||||
if os.name == 'nt':
|
||||
# Fix the symlinks on Windows since GIT is not creating them upon checkout
|
||||
# Fix the symlinks on Windows if GIT is not configured to create them upon checkout
|
||||
for lineage in ['a.encryption-example.com', 'b.encryption-example.com']:
|
||||
current_live = os.path.join(copied, 'live', lineage)
|
||||
for name in os.listdir(current_live):
|
||||
if name != 'README':
|
||||
current_file = os.path.join(current_live, name)
|
||||
with open(current_file) as file_h:
|
||||
src = file_h.read()
|
||||
os.unlink(current_file)
|
||||
os.symlink(os.path.join(current_live, src), current_file)
|
||||
if not os.path.islink(current_file):
|
||||
with open(current_file) as file_h:
|
||||
src = file_h.read()
|
||||
os.unlink(current_file)
|
||||
os.symlink(os.path.join(current_live, src), current_file)
|
||||
|
||||
return copied
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ Named Arguments
|
|||
DNS to propagate before asking the
|
||||
ACME server to verify the DNS
|
||||
record.
|
||||
(Default: 1200 because Linode
|
||||
updates its first DNS every 15
|
||||
minutes and we allow 5 more minutes
|
||||
for the update to reach the other 5
|
||||
(Default: 120 because Linode
|
||||
updates its first DNS every 60
|
||||
seconds and we allow 60 more seconds
|
||||
for the update to reach other 5
|
||||
servers)
|
||||
========================================== ===================================
|
||||
|
||||
|
|
@ -80,15 +80,15 @@ Examples
|
|||
-d www.example.com
|
||||
|
||||
.. code-block:: bash
|
||||
:caption: To acquire a certificate for ``example.com``, waiting 1000 seconds
|
||||
for DNS propagation (Linode updates its first DNS every 15 minutes
|
||||
and we allow some extra time for the update to reach the other 5
|
||||
:caption: To acquire a certificate for ``example.com``, waiting 120 seconds
|
||||
for DNS propagation (Linode updates its first DNS every minute
|
||||
and we allow some extra time for the update to reach other 5
|
||||
servers)
|
||||
|
||||
certbot certonly \\
|
||||
--dns-linode \\
|
||||
--dns-linode-credentials ~/.secrets/certbot/linode.ini \\
|
||||
--dns-linode-propagation-seconds 1000 \\
|
||||
--dns-linode-propagation-seconds 120 \\
|
||||
-d example.com
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class Authenticator(dns_common.DNSAuthenticator):
|
|||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
|
||||
super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=1200)
|
||||
super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=120)
|
||||
add('credentials', help='Linode credentials INI file.')
|
||||
|
||||
def more_info(self): # pylint: disable=missing-function-docstring
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
|||
|
||||
### Added
|
||||
|
||||
*
|
||||
* Third-party plugins can be used without prefix (`plugin_name` instead of `dist_name:plugin_name`):
|
||||
this concerns the plugin name, CLI flags, and keys in credential files.
|
||||
The prefixed form is still supported but is deprecated, and will be removed in a future release.
|
||||
|
||||
### Changed
|
||||
|
||||
*
|
||||
* The Linode DNS plugin now waits 120 seconds for DNS propagation, instead of 1200,
|
||||
due to https://www.linode.com/blog/linode/linode-turns-17/
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -23,47 +23,58 @@ except ImportError: # pragma: no cover
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PREFIX_FREE_DISTRIBUTIONS = [
|
||||
"certbot",
|
||||
"certbot-apache",
|
||||
"certbot-dns-cloudflare",
|
||||
"certbot-dns-cloudxns",
|
||||
"certbot-dns-digitalocean",
|
||||
"certbot-dns-dnsimple",
|
||||
"certbot-dns-dnsmadeeasy",
|
||||
"certbot-dns-gehirn",
|
||||
"certbot-dns-google",
|
||||
"certbot-dns-linode",
|
||||
"certbot-dns-luadns",
|
||||
"certbot-dns-nsone",
|
||||
"certbot-dns-ovh",
|
||||
"certbot-dns-rfc2136",
|
||||
"certbot-dns-route53",
|
||||
"certbot-dns-sakuracloud",
|
||||
"certbot-nginx",
|
||||
]
|
||||
"""Distributions for which prefix will be omitted."""
|
||||
|
||||
|
||||
class PluginEntryPoint(object):
|
||||
"""Plugin entry point."""
|
||||
|
||||
PREFIX_FREE_DISTRIBUTIONS = [
|
||||
"certbot",
|
||||
"certbot-apache",
|
||||
"certbot-dns-cloudflare",
|
||||
"certbot-dns-cloudxns",
|
||||
"certbot-dns-digitalocean",
|
||||
"certbot-dns-dnsimple",
|
||||
"certbot-dns-dnsmadeeasy",
|
||||
"certbot-dns-gehirn",
|
||||
"certbot-dns-google",
|
||||
"certbot-dns-linode",
|
||||
"certbot-dns-luadns",
|
||||
"certbot-dns-nsone",
|
||||
"certbot-dns-ovh",
|
||||
"certbot-dns-rfc2136",
|
||||
"certbot-dns-route53",
|
||||
"certbot-dns-sakuracloud",
|
||||
"certbot-nginx",
|
||||
]
|
||||
"""Distributions for which prefix will be omitted."""
|
||||
|
||||
# this object is mutable, don't allow it to be hashed!
|
||||
__hash__ = None # type: ignore
|
||||
|
||||
def __init__(self, entry_point):
|
||||
self.name = self.entry_point_to_plugin_name(entry_point)
|
||||
def __init__(self, entry_point, with_prefix=False):
|
||||
self.name = self.entry_point_to_plugin_name(entry_point, with_prefix)
|
||||
self.plugin_cls = entry_point.load()
|
||||
self.entry_point = entry_point
|
||||
self.warning_message = None
|
||||
self._initialized = None
|
||||
self._prepared = None
|
||||
self._hidden = False
|
||||
self._long_description = None
|
||||
|
||||
def check_name(self, name):
|
||||
"""Check if the name refers to this plugin."""
|
||||
if name == self.name:
|
||||
if self.warning_message:
|
||||
logger.warning(self.warning_message)
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def entry_point_to_plugin_name(cls, entry_point):
|
||||
def entry_point_to_plugin_name(cls, entry_point, with_prefix):
|
||||
"""Unique plugin name for an ``entry_point``"""
|
||||
if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS:
|
||||
return entry_point.name
|
||||
return entry_point.dist.key + ":" + entry_point.name
|
||||
if with_prefix:
|
||||
return entry_point.dist.key + ":" + entry_point.name
|
||||
return entry_point.name
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
|
|
@ -78,15 +89,25 @@ class PluginEntryPoint(object):
|
|||
@property
|
||||
def long_description(self):
|
||||
"""Long description of the plugin."""
|
||||
if self._long_description:
|
||||
return self._long_description
|
||||
try:
|
||||
return self.plugin_cls.long_description
|
||||
except AttributeError:
|
||||
return self.description
|
||||
|
||||
@long_description.setter
|
||||
def long_description(self, description):
|
||||
self._long_description = description
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""Should this plugin be hidden from UI?"""
|
||||
return getattr(self.plugin_cls, "hidden", False)
|
||||
return self._hidden or getattr(self.plugin_cls, "hidden", False)
|
||||
|
||||
@hidden.setter
|
||||
def hidden(self, hide):
|
||||
self._hidden = hide
|
||||
|
||||
def ifaces(self, *ifaces_groups):
|
||||
"""Does plugin implements specified interface groups?"""
|
||||
|
|
@ -212,16 +233,34 @@ class PluginsRegistry(Mapping):
|
|||
pkg_resources.iter_entry_points(
|
||||
constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT),)
|
||||
for entry_point in entry_points:
|
||||
plugin_ep = PluginEntryPoint(entry_point)
|
||||
assert plugin_ep.name not in plugins, (
|
||||
"PREFIX_FREE_DISTRIBUTIONS messed up")
|
||||
if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls):
|
||||
plugins[plugin_ep.name] = plugin_ep
|
||||
else: # pragma: no cover
|
||||
logger.warning(
|
||||
"%r does not provide IPluginFactory, skipping", plugin_ep)
|
||||
plugin_ep = cls._load_entry_point(entry_point, plugins, with_prefix=False)
|
||||
if entry_point.dist.key not in PREFIX_FREE_DISTRIBUTIONS:
|
||||
prefixed_plugin_ep = cls._load_entry_point(entry_point, plugins, with_prefix=True)
|
||||
prefixed_plugin_ep.hidden = True
|
||||
message = (
|
||||
"Plugin legacy name {0} may be removed in a future version. "
|
||||
"Please use {1} instead.").format(prefixed_plugin_ep.name, plugin_ep.name)
|
||||
prefixed_plugin_ep.warning_message = message
|
||||
prefixed_plugin_ep.long_description = "(WARNING: {0}) {1}".format(
|
||||
message, prefixed_plugin_ep.long_description)
|
||||
|
||||
return cls(plugins)
|
||||
|
||||
@classmethod
|
||||
def _load_entry_point(cls, entry_point, plugins, with_prefix):
|
||||
plugin_ep = PluginEntryPoint(entry_point, with_prefix)
|
||||
if plugin_ep.name in plugins:
|
||||
other_ep = plugins[plugin_ep.name]
|
||||
raise Exception("Duplicate plugin name {0} from {1} and {2}.".format(
|
||||
plugin_ep.name, plugin_ep.entry_point.dist.key, other_ep.entry_point.dist.key))
|
||||
if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls):
|
||||
plugins[plugin_ep.name] = plugin_ep
|
||||
else: # pragma: no cover
|
||||
logger.warning(
|
||||
"%r does not provide IPluginFactory, skipping", plugin_ep)
|
||||
|
||||
return plugin_ep
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self._plugins[name]
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ def pick_authenticator(
|
|||
return pick_plugin(
|
||||
config, default, plugins, question, (interfaces.IAuthenticator,))
|
||||
|
||||
|
||||
def get_unprepared_installer(config, plugins):
|
||||
"""
|
||||
Get an unprepared interfaces.IInstaller object.
|
||||
|
|
@ -53,7 +54,7 @@ def get_unprepared_installer(config, plugins):
|
|||
_, req_inst = cli_plugin_requests(config)
|
||||
if not req_inst:
|
||||
return None
|
||||
installers = plugins.filter(lambda p_ep: p_ep.name == req_inst)
|
||||
installers = plugins.filter(lambda p_ep: p_ep.check_name(req_inst))
|
||||
installers.init(config)
|
||||
installers = installers.verify((interfaces.IInstaller,))
|
||||
if len(installers) > 1:
|
||||
|
|
@ -67,6 +68,7 @@ def get_unprepared_installer(config, plugins):
|
|||
raise errors.PluginSelectionError(
|
||||
"Could not select or initialize the requested installer %s." % req_inst)
|
||||
|
||||
|
||||
def pick_plugin(config, default, plugins, question, ifaces):
|
||||
"""Pick plugin.
|
||||
|
||||
|
|
@ -84,7 +86,7 @@ def pick_plugin(config, default, plugins, question, ifaces):
|
|||
"""
|
||||
if default is not None:
|
||||
# throw more UX-friendly error if default not in plugins
|
||||
filtered = plugins.filter(lambda p_ep: p_ep.name == default)
|
||||
filtered = plugins.filter(lambda p_ep: p_ep.check_name(default))
|
||||
else:
|
||||
if config.noninteractive_mode:
|
||||
# it's really bad to auto-select the single available plugin in
|
||||
|
|
|
|||
|
|
@ -281,6 +281,7 @@ proxmox_ N Y Install certificates in Proxmox Virtualization serv
|
|||
dns-standalone_ Y N Obtain certificates via an integrated DNS server
|
||||
dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server
|
||||
dns-clouddns_ Y N DNS Authentication using CloudDNS API
|
||||
dns-inwx Y Y DNS Authentication for INWX through the XML API
|
||||
================== ==== ==== ===============================================================
|
||||
|
||||
.. _haproxy: https://github.com/greenhost/certbot-haproxy
|
||||
|
|
@ -293,6 +294,7 @@ dns-clouddns_ Y N DNS Authentication using CloudDNS API
|
|||
.. _dns-standalone: https://github.com/siilike/certbot-dns-standalone
|
||||
.. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig
|
||||
.. _dns-clouddns: https://github.com/vshosting/certbot-dns-clouddns
|
||||
.. _dns-inwx: https://github.com/oGGy990/certbot-dns-inwx/
|
||||
|
||||
If you're interested, you can also :ref:`write your own plugin <dev-plugin>`.
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ elif sys.version_info < (3,3):
|
|||
|
||||
dev_extras = [
|
||||
'coverage',
|
||||
'ipdb',
|
||||
'pytest',
|
||||
'pytest-cov',
|
||||
'pytest-xdist',
|
||||
|
|
@ -94,6 +93,7 @@ dev_extras = [
|
|||
|
||||
dev3_extras = [
|
||||
'astroid',
|
||||
'ipdb',
|
||||
'mypy',
|
||||
'pylint',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import unittest
|
|||
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock
|
||||
import pkg_resources
|
||||
import six
|
||||
|
|
@ -13,6 +13,7 @@ import zope.interface
|
|||
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot._internal.plugins import null
|
||||
from certbot._internal.plugins import standalone
|
||||
from certbot._internal.plugins import webroot
|
||||
|
||||
|
|
@ -44,7 +45,22 @@ class PluginEntryPointTest(unittest.TestCase):
|
|||
from certbot._internal.plugins.disco import PluginEntryPoint
|
||||
self.plugin_ep = PluginEntryPoint(EP_SA)
|
||||
|
||||
def test_entry_point_to_plugin_name(self):
|
||||
def test_entry_point_to_plugin_name_not_prefixed(self):
|
||||
from certbot._internal.plugins.disco import PluginEntryPoint
|
||||
|
||||
names = {
|
||||
self.ep1: "ep1",
|
||||
self.ep1prim: "ep1",
|
||||
self.ep2: "ep2",
|
||||
self.ep3: "ep3",
|
||||
EP_SA: "sa",
|
||||
}
|
||||
|
||||
for entry_point, name in six.iteritems(names):
|
||||
self.assertEqual(
|
||||
name, PluginEntryPoint.entry_point_to_plugin_name(entry_point, with_prefix=False))
|
||||
|
||||
def test_entry_point_to_plugin_name_prefixed(self):
|
||||
from certbot._internal.plugins.disco import PluginEntryPoint
|
||||
|
||||
names = {
|
||||
|
|
@ -52,12 +68,11 @@ class PluginEntryPointTest(unittest.TestCase):
|
|||
self.ep1prim: "p2:ep1",
|
||||
self.ep2: "p2:ep2",
|
||||
self.ep3: "p3:ep3",
|
||||
EP_SA: "sa",
|
||||
}
|
||||
|
||||
for entry_point, name in six.iteritems(names):
|
||||
self.assertEqual(
|
||||
name, PluginEntryPoint.entry_point_to_plugin_name(entry_point))
|
||||
name, PluginEntryPoint.entry_point_to_plugin_name(entry_point, with_prefix=True))
|
||||
|
||||
def test_description(self):
|
||||
self.assertTrue("temporary webserver" in self.plugin_ep.description)
|
||||
|
|
@ -197,17 +212,28 @@ class PluginsRegistryTest(unittest.TestCase):
|
|||
self.plugin_ep.__hash__.side_effect = TypeError
|
||||
self.plugins = {self.plugin_ep.name: self.plugin_ep}
|
||||
self.reg = self._create_new_registry(self.plugins)
|
||||
self.ep1 = pkg_resources.EntryPoint(
|
||||
"ep1", "p1.ep1", dist=mock.MagicMock(key="p1"))
|
||||
|
||||
def test_find_all(self):
|
||||
from certbot._internal.plugins.disco import PluginsRegistry
|
||||
with mock.patch("certbot._internal.plugins.disco.pkg_resources") as mock_pkg:
|
||||
mock_pkg.iter_entry_points.side_effect = [iter([EP_SA]),
|
||||
iter([EP_WR])]
|
||||
plugins = PluginsRegistry.find_all()
|
||||
mock_pkg.iter_entry_points.side_effect = [
|
||||
iter([EP_SA]), iter([EP_WR, self.ep1])
|
||||
]
|
||||
with mock.patch.object(pkg_resources.EntryPoint, 'load') as mock_load:
|
||||
mock_load.side_effect = [
|
||||
standalone.Authenticator, webroot.Authenticator,
|
||||
null.Installer, null.Installer]
|
||||
plugins = PluginsRegistry.find_all()
|
||||
self.assertTrue(plugins["sa"].plugin_cls is standalone.Authenticator)
|
||||
self.assertTrue(plugins["sa"].entry_point is EP_SA)
|
||||
self.assertTrue(plugins["wr"].plugin_cls is webroot.Authenticator)
|
||||
self.assertTrue(plugins["wr"].entry_point is EP_WR)
|
||||
self.assertTrue(plugins["ep1"].plugin_cls is null.Installer)
|
||||
self.assertTrue(plugins["ep1"].entry_point is self.ep1)
|
||||
self.assertTrue(plugins["p1:ep1"].plugin_cls is null.Installer)
|
||||
self.assertTrue(plugins["p1:ep1"].entry_point is self.ep1)
|
||||
|
||||
def test_getitem(self):
|
||||
self.assertEqual(self.plugin_ep, self.reg["mock"])
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ class ChoosePluginTest(unittest.TestCase):
|
|||
|
||||
self.assertTrue("default" in mock_util().menu.call_args[1])
|
||||
|
||||
|
||||
class GetUnpreparedInstallerTest(test_util.ConfigTestCase):
|
||||
"""Tests for certbot._internal.plugins.selection.get_unprepared_installer."""
|
||||
|
||||
|
|
@ -181,10 +182,10 @@ class GetUnpreparedInstallerTest(test_util.ConfigTestCase):
|
|||
super(GetUnpreparedInstallerTest, self).setUp()
|
||||
self.mock_apache_fail_ep = mock.Mock(
|
||||
description_with_name="afail")
|
||||
self.mock_apache_fail_ep.name = "afail"
|
||||
self.mock_apache_fail_ep.check_name = lambda name: name == "afail"
|
||||
self.mock_apache_ep = mock.Mock(
|
||||
description_with_name="apache")
|
||||
self.mock_apache_ep.name = "apache"
|
||||
self.mock_apache_ep.check_name = lambda name: name == "apache"
|
||||
self.mock_apache_plugin = mock.MagicMock()
|
||||
self.mock_apache_ep.init.return_value = self.mock_apache_plugin
|
||||
self.plugins = PluginsRegistry({
|
||||
|
|
@ -213,7 +214,7 @@ class GetUnpreparedInstallerTest(test_util.ConfigTestCase):
|
|||
def test_multiple_installers_returned(self):
|
||||
self.config.configurator = "apache"
|
||||
# Two plugins with the same name
|
||||
self.mock_apache_fail_ep.name = "apache"
|
||||
self.mock_apache_fail_ep.check_name = lambda name: name == "apache"
|
||||
self.assertRaises(errors.PluginSelectionError, self._call)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ description: |
|
|||
- Help you revoke the certificate if that ever becomes necessary.
|
||||
confinement: classic
|
||||
base: core20
|
||||
grade: stable
|
||||
adopt-info: certbot
|
||||
|
||||
apps:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ asn1crypto==0.22.0
|
|||
astroid==2.3.3
|
||||
attrs==17.3.0
|
||||
Babel==2.5.1
|
||||
backcall==0.2.0
|
||||
backports.functools-lru-cache==1.5
|
||||
backports.shutil-get-terminal-size==1.0.0
|
||||
backports.ssl-match-hostname==3.7.0.1
|
||||
|
|
@ -40,9 +41,10 @@ httplib2==0.10.3
|
|||
imagesize==0.7.1
|
||||
importlib-metadata==0.23
|
||||
ipdb==0.12.3
|
||||
ipython==5.8.0
|
||||
ipython==7.9.0
|
||||
ipython-genutils==0.2.0
|
||||
isort==4.3.21
|
||||
jedi==0.17.1
|
||||
Jinja2==2.9.6
|
||||
jmespath==0.9.4
|
||||
josepy==1.1.0
|
||||
|
|
@ -59,13 +61,14 @@ ndg-httpsclient==0.3.2
|
|||
oauth2client==4.0.0
|
||||
packaging==19.2
|
||||
paramiko==2.4.2
|
||||
parso==0.7.0
|
||||
pathlib2==2.3.5
|
||||
pexpect==4.7.0
|
||||
pickleshare==0.7.5
|
||||
pkginfo==1.4.2
|
||||
pluggy==0.13.0
|
||||
ply==3.4
|
||||
prompt-toolkit==1.0.18
|
||||
prompt-toolkit==2.0.10
|
||||
ptyprocess==0.6.0
|
||||
py==1.8.0
|
||||
pyasn1==0.1.9
|
||||
|
|
|
|||
|
|
@ -63,3 +63,10 @@ dns-lexicon==2.2.1
|
|||
# Tracking at https://github.com/certbot/certbot/issues/6473
|
||||
boto3==1.4.7
|
||||
botocore==1.7.41
|
||||
|
||||
# Old certbot[dev] constraints
|
||||
# Old versions of certbot[dev] required ipdb and our normally pinned version of
|
||||
# ipython which ipdb depends on doesn't support Python 2 so we pin an older
|
||||
# version here to keep tests working while we have Python 2 support.
|
||||
ipython==5.8.0
|
||||
prompt-toolkit==1.0.18
|
||||
|
|
|
|||
Loading…
Reference in a new issue