From 68b3b048b9572e3901c145861c9e1add659a4ad7 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 10 Jul 2020 18:16:21 +0200 Subject: [PATCH 1/6] Use 3rd party plugins without prefix + set a deprecation path for the prefixed version (#8131) Fixes #4351 This PR proposes a solution to use the third party plugins with the prefix `pip_package_name:` in the plugin name, plugin specific flags and keys in dns plugin credential files. A first solution has been proposed in #6372, and a more advanced one in #7026. In #7026 was also added a deprecation warning when the old plugin name `pip_package_name:plugin_name` was used. However there were some limitations with #7026, in particular the fact that existing flags of type `pip_package_name:dns_plugin_option` or keys like `pip_package_name:key` in dns plugin credential files were not read anymore. This would have led to silent failures during renewals if the configuration was not explicitly updated by the user. I tried to fix that based on #7026, but the changes needed are complex, and create new problems on their own, like unexpected erasure of values in the renewal configurations. Instead I try in this PR a new approach: the `PluginsRegistry` in `certbot._internal.plugins.disco` module register two plugins for a given entrypoint refering to a third party plugin when `find_all()` is called: * one plugin with the name `plugin_name` * one plugin with the name `pip_package_name:plugin_name` (like before) This way, every existing configuration continues to work without any change (credentials, renewal configuration, CLI flags). And new configurations can refer to the new plugin name without prefix, and use the approriate CLI flags, credentials without this prefix. On top of it I added the deprecation path given in #7026 (thanks @coldfix!): * the plugin named `pip_package_name:plugin_name` is hidden from `certbot plugins` output * the help for this plugin is still displayed, and a deprecation warning is displayed in the description * when invoked, the same deprecation warning is displayed in the terminal * Support both prefixed and not prefix third party plugins * Adapt tests * Add deprecation path * Named parameters * Add deprecation warning in CLI * Add a changelog --- certbot/CHANGELOG.md | 4 +- certbot/certbot/_internal/plugins/disco.py | 111 ++++++++++++------ .../certbot/_internal/plugins/selection.py | 6 +- certbot/tests/plugins/disco_test.py | 40 +++++-- certbot/tests/plugins/selection_test.py | 7 +- 5 files changed, 119 insertions(+), 49 deletions(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index c127d83a6..8124d1e0c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -6,7 +6,9 @@ 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 diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index 4cce895d8..4538e8123 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -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] diff --git a/certbot/certbot/_internal/plugins/selection.py b/certbot/certbot/_internal/plugins/selection.py index 53cef3969..0b04791c6 100644 --- a/certbot/certbot/_internal/plugins/selection.py +++ b/certbot/certbot/_internal/plugins/selection.py @@ -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 diff --git a/certbot/tests/plugins/disco_test.py b/certbot/tests/plugins/disco_test.py index 5a0a392b0..ed13544de 100644 --- a/certbot/tests/plugins/disco_test.py +++ b/certbot/tests/plugins/disco_test.py @@ -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"]) diff --git a/certbot/tests/plugins/selection_test.py b/certbot/tests/plugins/selection_test.py index e5e6db031..a5de99e60 100644 --- a/certbot/tests/plugins/selection_test.py +++ b/certbot/tests/plugins/selection_test.py @@ -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) From 05dbda4b519daa1db08a0cf0a730b9240e37c65b Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 15 Jul 2020 22:41:15 +0200 Subject: [PATCH 2/6] added inwx plugin (#8115) * added inwx plugin * Update using.rst fixed convention naming --- certbot/docs/using.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst index 0a0b6d1a2..0423b1fec 100644 --- a/certbot/docs/using.rst +++ b/certbot/docs/using.rst @@ -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 `. From 9d2e0ac013b9b2accc70f1a75584a51d335e7463 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 Jul 2020 12:47:11 -0700 Subject: [PATCH 3/6] Specify the Certbot snap grade. (#8147) --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index df6149551..06e44e3d5 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -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: From ebf1349b15098548b0dcad30a0dabd07ed3f11d1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 Jul 2020 13:01:04 -0700 Subject: [PATCH 4/6] Update to IPython with Python 3.8 support. (#8152) --- certbot/setup.py | 2 +- tools/dev_constraints.txt | 7 +++++-- tools/oldest_constraints.txt | 7 +++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/certbot/setup.py b/certbot/setup.py index b2e0837d3..efa7e3c28 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -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', ] diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 6e692841b..31ca577d5 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -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 diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index ff4b8361a..5145e3ddf 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -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 From b6964cae2e9ad6e9bd316c3dfb3d3b1c322c958b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20Talpalaru?= Date: Sun, 19 Jul 2020 16:32:30 +0200 Subject: [PATCH 5/6] certbot_dns_linode: decrease the default propagation interval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit «When you add or change DNS zones or records, your changes will now be reflected at our authoritative nameservers in under 60 seconds. This is down from the previous “every quarter hour” approach that we had for so long.» - https://www.linode.com/blog/linode/linode-turns-17/ --- AUTHORS.md | 1 + .../certbot_dns_linode/__init__.py | 16 ++++++++-------- .../certbot_dns_linode/_internal/dns_linode.py | 2 +- certbot/CHANGELOG.md | 3 ++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 6b6b5d118..0cedcbd19 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -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) diff --git a/certbot-dns-linode/certbot_dns_linode/__init__.py b/certbot-dns-linode/certbot_dns_linode/__init__.py index 107781a13..4bfd95573 100644 --- a/certbot-dns-linode/certbot_dns_linode/__init__.py +++ b/certbot-dns-linode/certbot_dns_linode/__init__.py @@ -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 """ diff --git a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py index f7b3ec3d4..f9450c02c 100644 --- a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py +++ b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py @@ -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 diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 8124d1e0c..58eb03984 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -12,7 +12,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### 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 From b13dfc6437ab14bff801607c6124292aefcc48b2 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 21 Jul 2020 01:01:09 +0200 Subject: [PATCH 6/6] Do not create the symlink for test assets on Windows if the asset path is already a symlink (#8159) --- certbot-ci/certbot_integration_tests/utils/misc.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 9d1676c60..38c2e60a8 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -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