diff --git a/.azure-pipelines/templates/jobs/extended-tests-jobs.yml b/.azure-pipelines/templates/jobs/extended-tests-jobs.yml index 8d95ebdb6..482f3ae8b 100644 --- a/.azure-pipelines/templates/jobs/extended-tests-jobs.yml +++ b/.azure-pipelines/templates/jobs/extended-tests-jobs.yml @@ -4,7 +4,7 @@ jobs: - name: IMAGE_NAME value: ubuntu-18.04 - name: PYTHON_VERSION - value: 3.9 + value: 3.10 - group: certbot-common strategy: matrix: @@ -17,6 +17,9 @@ jobs: linux-py38: PYTHON_VERSION: 3.8 TOXENV: py38 + linux-py39: + PYTHON_VERSION: 3.9 + TOXENV: py39 linux-py37-nopin: PYTHON_VERSION: 3.7 TOXENV: py37 @@ -71,6 +74,14 @@ jobs: PYTHON_VERSION: 3.9 TOXENV: integration ACME_SERVER: boulder-v2 + linux-boulder-v1-py310-integration: + PYTHON_VERSION: 3.10 + TOXENV: integration + ACME_SERVER: boulder-v1 + linux-boulder-v2-py310-integration: + PYTHON_VERSION: 3.10 + TOXENV: integration + ACME_SERVER: boulder-v2 nginx-compat: TOXENV: nginx_compat linux-integration-rfc2136: diff --git a/.azure-pipelines/templates/jobs/standard-tests-jobs.yml b/.azure-pipelines/templates/jobs/standard-tests-jobs.yml index f21be20f0..c3d820584 100644 --- a/.azure-pipelines/templates/jobs/standard-tests-jobs.yml +++ b/.azure-pipelines/templates/jobs/standard-tests-jobs.yml @@ -1,16 +1,16 @@ jobs: - job: test variables: - PYTHON_VERSION: 3.9 + PYTHON_VERSION: 3.10 strategy: matrix: macos-py36: IMAGE_NAME: macOS-10.15 PYTHON_VERSION: 3.6 TOXENV: py36 - macos-py39: + macos-py310: IMAGE_NAME: macOS-10.15 - PYTHON_VERSION: 3.9 + PYTHON_VERSION: 3.10 TOXENV: py39 windows-py36: IMAGE_NAME: vs2017-win2016 @@ -36,17 +36,17 @@ jobs: IMAGE_NAME: ubuntu-18.04 PYTHON_VERSION: 3.6 TOXENV: py36 - linux-py39-cover: + linux-py310-cover: IMAGE_NAME: ubuntu-18.04 - PYTHON_VERSION: 3.9 - TOXENV: py39-cover - linux-py39-lint: + PYTHON_VERSION: 3.10 + TOXENV: py310-cover + linux-py310-lint: IMAGE_NAME: ubuntu-18.04 - PYTHON_VERSION: 3.9 + PYTHON_VERSION: 3.10 TOXENV: lint-posix - linux-py39-mypy: + linux-py310-mypy: IMAGE_NAME: ubuntu-18.04 - PYTHON_VERSION: 3.9 + PYTHON_VERSION: 3.10 TOXENV: mypy-posix linux-integration: IMAGE_NAME: ubuntu-18.04 diff --git a/acme/setup.py b/acme/setup.py index 7e0ce9b96..86870abae 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -45,6 +45,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index c54afb4d9..cf4019ef3 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -2,7 +2,6 @@ # pylint: disable=too-many-lines from collections import defaultdict import copy -from distutils.version import LooseVersion import fnmatch import logging import re @@ -154,9 +153,10 @@ class ApacheConfigurator(common.Installer, interfaces.Authenticator): """ # Disabling TLS session tickets is supported by Apache 2.4.11+ and OpenSSL 1.0.2l+. # So for old versions of Apache we pick a configuration without this option. + min_openssl_version = util.parse_loose_version('1.0.2l') openssl_version = self.openssl_version(warn_on_no_mod_ssl) if self.version < (2, 4, 11) or not openssl_version or\ - LooseVersion(openssl_version) < LooseVersion('1.0.2l'): + util.parse_loose_version(openssl_version) < min_openssl_version: return apache_util.find_ssl_apache_conf("old") return apache_util.find_ssl_apache_conf("current") diff --git a/certbot-apache/certbot_apache/_internal/entrypoint.py b/certbot-apache/certbot_apache/_internal/entrypoint.py index 96bef030c..4311b11c6 100644 --- a/certbot-apache/certbot_apache/_internal/entrypoint.py +++ b/certbot-apache/certbot_apache/_internal/entrypoint.py @@ -1,6 +1,4 @@ """ Entry point for Apache Plugin """ -from distutils.version import LooseVersion - from certbot import util from certbot_apache._internal import configurator from certbot_apache._internal import override_arch @@ -47,7 +45,8 @@ def get_configurator(): override_class = None # Special case for older Fedora versions - if os_name == 'fedora' and LooseVersion(os_version) < LooseVersion('29'): + min_version = util.parse_loose_version('29') + if os_name == 'fedora' and util.parse_loose_version(os_version) < min_version: os_name = 'fedora_old' try: diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 5386cecf6..caaa0e2ea 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -38,6 +38,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-ci/certbot_integration_tests/utils/certbot_call.py b/certbot-ci/certbot_integration_tests/utils/certbot_call.py index 570a02bcb..ffd14ac4b 100755 --- a/certbot-ci/certbot_integration_tests/utils/certbot_call.py +++ b/certbot-ci/certbot_integration_tests/utils/certbot_call.py @@ -1,8 +1,8 @@ #!/usr/bin/env python """Module to call certbot in test mode""" -from distutils.version import LooseVersion import os +import pkg_resources import subprocess import sys @@ -85,7 +85,7 @@ def _compute_additional_args(workspace, environ, force_renew): cwd=workspace, env=environ) # Typical response is: output = 'certbot 0.31.0.dev0' version_str = output.split(' ')[1].strip() - if LooseVersion(version_str) >= LooseVersion('0.30.0'): + if pkg_resources.parse_version(version_str) >= pkg_resources.parse_version('0.30.0'): additional_args.append('--no-random-sleep-on-renew') if force_renew: diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 9a09a2b9c..769705ad7 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -1,5 +1,4 @@ -from distutils.version import LooseVersion - +from pkg_resources import parse_version from setuptools import __version__ as setuptools_version from setuptools import find_packages from setuptools import setup @@ -10,7 +9,7 @@ version = '0.32.0.dev0' min_setuptools_version='36.2' # This conditional isn't necessary, but it provides better error messages to # people who try to install this package with older versions of setuptools. -if LooseVersion(setuptools_version) < LooseVersion(min_setuptools_version): +if parse_version(setuptools_version) < parse_version(min_setuptools_version): raise RuntimeError(f'setuptools {min_setuptools_version}+ is required') install_requires = [ @@ -29,6 +28,7 @@ install_requires = [ 'pywin32>=300 ; sys_platform == "win32"', 'pyyaml', 'requests', + 'setuptools', 'types-python-dateutil' ] @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 8004d92fb..e66ed738b 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -29,6 +29,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 986989e57..96f0ddad0 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 9f7b8dc07..300246b0f 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index e43b7bc22..e8472a9b9 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 1d7c890b5..0a287ac2e 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -53,6 +53,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index d9a3f6fc4..37b7f7942 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 1b6aca116..c1f322329 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 28b1d43a5..1838c2737 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -54,6 +54,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index c516a869b..a41d7fe20 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index b7a6abe66..a4d246fd7 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 2c4bd85a5..410f26ee0 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index f639554b6..1ceec6646 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 0df6aa858..d39199829 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 70c7c981e..d8ec6439d 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 0ab50bfda..e7bae6f51 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index a9a33d529..596890cd1 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1,6 +1,5 @@ # pylint: disable=too-many-lines """Nginx Configuration""" -from distutils.version import LooseVersion import logging import re import socket @@ -142,8 +141,9 @@ class NginxConfigurator(common.Installer, interfaces.Authenticator): # For a complete history, check out https://github.com/certbot/certbot/issues/7322 use_tls13 = self.version >= (1, 13, 0) + min_openssl_version = util.parse_loose_version('1.0.2l') session_tix_off = self.version >= (1, 5, 9) and self.openssl_version and\ - LooseVersion(self.openssl_version) >= LooseVersion('1.0.2l') + util.parse_loose_version(self.openssl_version) >= min_openssl_version if use_tls13: if session_tix_off: diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index d4a5cd874..59e2ea5f2 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -35,6 +35,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index f7cf90e09..76a1c3578 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -6,11 +6,15 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* Support for Python 3.10 was added to Certbot and all of its components. +* The function certbot.util.parse_loose_version was added to parse version + strings in the same way as the now deprecated distutils.version.LooseVersion + class from the Python standard library. ### Changed -* +* The function certbot.util.get_strict_version was deprecated and will be + removed in a future release. ### Fixed diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index d69dd6a00..927addb5e 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -12,6 +12,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey from cryptography.hazmat.primitives.serialization import load_pem_private_key import parsedatetime +import pkg_resources import pytz import certbot @@ -33,7 +34,7 @@ logger = logging.getLogger(__name__) ALL_FOUR = ("cert", "privkey", "chain", "fullchain") README = "README" -CURRENT_VERSION = util.get_strict_version(certbot.__version__) +CURRENT_VERSION = pkg_resources.parse_version(certbot.__version__) BASE_PRIVKEY_MODE = 0o600 @@ -457,7 +458,7 @@ class RenewableCert(interfaces.RenewableCert): conf_version = self.configuration.get("version") if (conf_version is not None and - util.get_strict_version(conf_version) > CURRENT_VERSION): + pkg_resources.parse_version(conf_version) > CURRENT_VERSION): logger.info( "Attempting to parse the version %s renewal configuration " "file found at %s with version %s of Certbot. This might not " diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py index 4e92688b9..2b3e8cae5 100644 --- a/certbot/certbot/util.py +++ b/certbot/certbot/util.py @@ -1,10 +1,7 @@ """Utilities for all Certbot.""" -# distutils.version under virtualenv confuses pylint -# For more info, see: https://github.com/PyCQA/pylint/issues/73 import argparse import atexit import collections -import distutils.version import errno import logging import platform @@ -14,6 +11,7 @@ import subprocess import sys from typing import Dict from typing import IO +from typing import List from typing import Text from typing import Tuple from typing import Union @@ -61,7 +59,7 @@ _INITIAL_PID = os.getpid() # program exits before the lock is cleaned up, it is automatically # released, but the file isn't deleted. _LOCKS: Dict[str, lock.LockFile] = {} - +_VERSION_COMPONENT_RE = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) def env_no_snap_for_external_calls(): """ @@ -612,8 +610,13 @@ def get_strict_version(normalized): :rtype: distutils.version.StrictVersion """ - # strict version ending with "a" and a number designates a pre-release - return distutils.version.StrictVersion(normalized.replace(".dev", "a")) + warnings.warn("certbot.util.get_strict_version is deprecated and will be " + "removed in a future release.", DeprecationWarning) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + import distutils.version + # strict version ending with "a" and a number designates a pre-release + return distutils.version.StrictVersion(normalized.replace(".dev", "a")) def is_staging(srv): @@ -639,6 +642,32 @@ def atexit_register(func, *args, **kwargs): atexit.register(_atexit_call, func, *args, **kwargs) +def parse_loose_version(version_string): + """Parses a version string into its components. + + This code and the returned tuple is based on the now deprecated + distutils.version.LooseVersion class from the Python standard library. + Two LooseVersion classes and two lists as returned by this function should + compare in the same way. See + https://github.com/python/cpython/blob/v3.10.0/Lib/distutils/version.py#L205-L347. + + :param str version_string: version string + + :returns: list of parsed version string components + :rtype: list + + """ + components: List[Union[int, str]] + components = [x for x in _VERSION_COMPONENT_RE.split(version_string) + if x and x != '.'] + for i, obj in enumerate(components): + try: + components[i] = int(obj) + except ValueError: + pass + return components + + def _atexit_call(func, *args, **kwargs): if _INITIAL_PID == os.getpid(): func(*args, **kwargs) diff --git a/certbot/setup.py b/certbot/setup.py index 9f4240cbf..5a6823053 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -1,9 +1,9 @@ import codecs -from distutils.version import LooseVersion import os import re import sys +from pkg_resources import parse_version from setuptools import __version__ as setuptools_version from setuptools import find_packages from setuptools import setup @@ -11,7 +11,7 @@ from setuptools import setup min_setuptools_version='39.0.1' # This conditional isn't necessary, but it provides better error messages to # people who try to install this package with older versions of setuptools. -if LooseVersion(setuptools_version) < LooseVersion(min_setuptools_version): +if parse_version(setuptools_version) < parse_version(min_setuptools_version): raise RuntimeError(f'setuptools {min_setuptools_version}+ is required') # Workaround for https://bugs.python.org/issue8876, see @@ -132,6 +132,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 418486f25..3af87f85a 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -592,6 +592,19 @@ class OsInfoTest(unittest.TestCase): self.assertEqual(cbutil.get_python_os_info(), ("testdist", "42")) +class GetStrictVersionTest(unittest.TestCase): + """Test for certbot.util.get_strict_version.""" + + @classmethod + def _call(cls, *args, **kwargs): + from certbot.util import get_strict_version + return get_strict_version(*args, **kwargs) + + def test_it(self): + with self.assertWarnsRegex(DeprecationWarning, "get_strict_version"): + self._call("1.2.3") + + class AtexitRegisterTest(unittest.TestCase): """Tests for certbot.util.atexit_register.""" def setUp(self): @@ -624,5 +637,38 @@ class AtexitRegisterTest(unittest.TestCase): atexit_func(*args[1:], **kwargs) +class ParseLooseVersionTest(unittest.TestCase): + """Test for certbot.util.parse_loose_version. + + These tests are based on the original tests for + distutils.version.LooseVersion at + https://github.com/python/cpython/blob/v3.10.0/Lib/distutils/tests/test_version.py#L58-L81. + + """ + + @classmethod + def _call(cls, *args, **kwargs): + from certbot.util import parse_loose_version + return parse_loose_version(*args, **kwargs) + + def test_less_than(self): + comparisons = (('1.5.1', '1.5.2b2'), + ('3.4j', '1996.07.12'), + ('2g6', '11g'), + ('0.960923', '2.2beta29'), + ('1.13++', '5.5.kw')) + for v1, v2 in comparisons: + self.assertLess(self._call(v1), self._call(v2)) + + def test_equal(self): + self.assertEqual(self._call('8.02'), self._call('8.02')) + + def test_greater_than(self): + comparisons = (('161', '3.10a'), + ('3.2.pl0', '3.1.1.6')) + for v1, v2 in comparisons: + self.assertGreater(self._call(v1), self._call(v2)) + + if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/letstest/scripts/test_openssl_version.py b/letstest/scripts/test_openssl_version.py index c55441c5d..94648c40c 100644 --- a/letstest/scripts/test_openssl_version.py +++ b/letstest/scripts/test_openssl_version.py @@ -1,8 +1,9 @@ #!/usr/bin/env python # Test script for OpenSSL version checking -from distutils.version import LooseVersion import sys +from certbot import util + def main(openssl_version, apache_version): if not openssl_version.strip(): @@ -12,8 +13,8 @@ def main(openssl_version, apache_version): conf_file_location = "/etc/letsencrypt/options-ssl-apache.conf" with open(conf_file_location) as f: contents = f.read() - if LooseVersion(apache_version.strip()) < LooseVersion('2.4.11') or \ - LooseVersion(openssl_version.strip()) < LooseVersion('1.0.2l'): + if util.parse_loose_version(apache_version.strip()) < util.parse_loose_version('2.4.11') or \ + util.parse_loose_version(openssl_version.strip()) < util.parse_loose_version('1.0.2l'): # should be old version # assert SSLSessionTickets not in conf file if "SSLSessionTickets" in contents: diff --git a/letstest/setup.py b/letstest/setup.py index a552cf920..6f0e9bb49 100644 --- a/letstest/setup.py +++ b/letstest/setup.py @@ -20,6 +20,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], diff --git a/pytest.ini b/pytest.ini index 5091f2836..92a403451 100644 --- a/pytest.ini +++ b/pytest.ini @@ -22,6 +22,11 @@ # the certbot.display.util module. # 5) A deprecation warning is raised in dnspython==1.15.0 in the oldest tests for # certbot-dns-rfc2136. +# 6) The vendored version of six in botocore causes ImportWarnings in Python +# 3.10+. See https://github.com/boto/botocore/issues/2548. +# 7) botocore's default TLS settings raise deprecation warnings in Python +# 3.10+, but their values are sane from a security perspective. See +# https://github.com/boto/botocore/issues/2550. filterwarnings = error ignore:The external mock module:PendingDeprecationWarning @@ -29,3 +34,5 @@ filterwarnings = ignore:.*attribute in certbot.interfaces module is deprecated:DeprecationWarning ignore:.*attribute in certbot.display.util module is deprecated:DeprecationWarning ignore:decodestring\(\) is a deprecated alias:DeprecationWarning:dns + ignore:_SixMetaPathImporter.:ImportWarning + ignore:ssl.PROTOCOL_TLS:DeprecationWarning:botocore diff --git a/tools/venv.py b/tools/venv.py index 4be597bd1..bd1250eed 100755 --- a/tools/venv.py +++ b/tools/venv.py @@ -14,7 +14,6 @@ variable VENV_NAME. from __future__ import print_function -from distutils.version import LooseVersion import glob import os import re @@ -200,31 +199,9 @@ def install_packages(venv_name, pip_args): # Using the python executable from venv, we ensure to execute following commands in this venv. py_venv = get_venv_python_path(venv_name) subprocess_with_print([py_venv, os.path.abspath('tools/pipstrap.py')]) - # We only use this value during pip install because: - # 1) We're really only adding it for installing cryptography, which happens here, and - # 2) There are issues with calling it along with VIRTUALENV_NO_DOWNLOAD, which applies at the - # steps above, not during pip install. - env_pip_no_binary = os.environ.get('CERTBOT_PIP_NO_BINARY') - if env_pip_no_binary: - # Check OpenSSL version. If it's too low, don't apply the env variable. - openssl_version_string = str(subprocess_output_with_print(['openssl', 'version'])) - matches = re.findall(r'OpenSSL ([^ ]+) ', openssl_version_string) - if not matches: - print('Could not find OpenSSL version, not setting PIP_NO_BINARY.') - else: - openssl_version = matches[0] - - if LooseVersion(openssl_version) >= LooseVersion('1.0.2'): - print('Setting PIP_NO_BINARY to {0}' - ' as specified in CERTBOT_PIP_NO_BINARY'.format(env_pip_no_binary)) - os.environ['PIP_NO_BINARY'] = env_pip_no_binary - else: - print('Not setting PIP_NO_BINARY, as OpenSSL version is too old.') command = [py_venv, os.path.abspath('tools/pip_install.py')] command.extend(pip_args) subprocess_with_print(command) - if 'PIP_NO_BINARY' in os.environ: - del os.environ['PIP_NO_BINARY'] if os.path.isdir(os.path.join(venv_name, 'bin')): # Linux/OSX specific diff --git a/windows-installer/setup.py b/windows-installer/setup.py index cddc9ea18..3d16a49f8 100644 --- a/windows-installer/setup.py +++ b/windows-installer/setup.py @@ -22,6 +22,7 @@ setup( 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Operating System :: Microsoft :: Windows', 'Topic :: Software Development :: Build Tools', ],