diff --git a/.azure-pipelines/templates/steps/tox-steps.yml b/.azure-pipelines/templates/steps/tox-steps.yml index ecf3d6032..3e5fb995d 100644 --- a/.azure-pipelines/templates/steps/tox-steps.yml +++ b/.azure-pipelines/templates/steps/tox-steps.yml @@ -1,6 +1,10 @@ steps: + # We run brew update because we've seen attempts to install an older version + # of a package fail. See + # https://github.com/actions/virtual-environments/issues/3165. - bash: | set -e + brew update brew install augeas condition: startswith(variables['IMAGE_NAME'], 'macOS') displayName: Install MacOS dependencies diff --git a/Dockerfile-dev b/Dockerfile-dev index 86847f8fd..895dbdc0b 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,5 +1,5 @@ # This Dockerfile builds an image for development. -FROM debian:buster +FROM ubuntu:focal # Note: this only exposes the port to other docker containers. EXPOSE 80 443 @@ -8,8 +8,9 @@ WORKDIR /opt/certbot/src COPY . . RUN apt-get update && \ - apt-get install apache2 git python3-dev python3-venv gcc libaugeas0 \ - libssl-dev libffi-dev ca-certificates openssl nginx-light -y && \ + DEBIAN_FRONTEND=noninteractive apt-get install apache2 git python3-dev \ + python3-venv gcc libaugeas0 libssl-dev libffi-dev ca-certificates \ + openssl nginx-light -y --no-install-recommends && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* \ /tmp/* \ diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 4fad9a421..2c8190be5 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -30,7 +30,7 @@ class Challenge(jose.TypedJSONObjectWithFields): @classmethod def from_json(cls, jobj): try: - return super(Challenge, cls).from_json(jobj) + return super().from_json(jobj) except jose.UnrecognizedTypeError as error: logger.debug(error) return UnrecognizedChallenge.from_json(jobj) @@ -58,7 +58,7 @@ class UnrecognizedChallenge(Challenge): """ def __init__(self, jobj): - super(UnrecognizedChallenge, self).__init__() + super().__init__() object.__setattr__(self, "jobj", jobj) def to_partial_json(self): @@ -141,7 +141,7 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): return True def to_partial_json(self): - jobj = super(KeyAuthorizationChallengeResponse, self).to_partial_json() + jobj = super().to_partial_json() jobj.pop('keyAuthorization', None) return jobj diff --git a/acme/acme/client.py b/acme/acme/client.py index ae4a64b23..548c3d548 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -253,7 +253,7 @@ class Client(ClientBase): if isinstance(directory, str): directory = messages.Directory.from_json( net.get(directory).json()) - super(Client, self).__init__(directory=directory, + super().__init__(directory=directory, net=net, acme_version=1) def register(self, new_reg=None): @@ -577,7 +577,7 @@ class ClientV2(ClientBase): :param .messages.Directory directory: Directory Resource :param .ClientNetwork net: Client network. """ - super(ClientV2, self).__init__(directory=directory, + super().__init__(directory=directory, net=net, acme_version=2) def new_account(self, new_account): @@ -627,7 +627,7 @@ class ClientV2(ClientBase): """ # https://github.com/certbot/certbot/issues/6155 new_regr = self._get_v2_account(regr) - return super(ClientV2, self).update_registration(new_regr, update) + return super().update_registration(new_regr, update) def _get_v2_account(self, regr): self.net.account = None diff --git a/acme/acme/errors.py b/acme/acme/errors.py index 5ca5a4fa2..b47ed88da 100644 --- a/acme/acme/errors.py +++ b/acme/acme/errors.py @@ -28,13 +28,8 @@ class NonceError(ClientError): class BadNonce(NonceError): """Bad nonce error.""" - def __init__(self, nonce, error, *args, **kwargs): - # MyPy complains here that there is too many arguments for BaseException constructor. - # This is an error fixed in typeshed, see https://github.com/python/mypy/issues/4183 - # The fix is included in MyPy>=0.740, but upgrading it would bring dozen of errors due to - # new types definitions. So we ignore the error until the code base is fixed to match - # with MyPy>=0.740 referential. - super(BadNonce, self).__init__(*args, **kwargs) # type: ignore + def __init__(self, nonce, error, *args): + super().__init__(*args) self.nonce = nonce self.error = error @@ -52,9 +47,8 @@ class MissingNonce(NonceError): :ivar requests.Response ~.response: HTTP Response """ - def __init__(self, response, *args, **kwargs): - # See comment in BadNonce constructor above for an explanation of type: ignore here. - super(MissingNonce, self).__init__(*args, **kwargs) # type: ignore + def __init__(self, response, *args): + super().__init__(*args) self.response = response def __str__(self): @@ -78,7 +72,7 @@ class PollError(ClientError): def __init__(self, exhausted, updated): self.exhausted = exhausted self.updated = updated - super(PollError, self).__init__() + super().__init__() @property def timeout(self): @@ -96,7 +90,7 @@ class ValidationError(Error): """ def __init__(self, failed_authzrs): self.failed_authzrs = failed_authzrs - super(ValidationError, self).__init__() + super().__init__() class TimeoutError(Error): # pylint: disable=redefined-builtin @@ -112,7 +106,7 @@ class IssuanceError(Error): :param messages.Error error: The error provided by the server. """ self.error = error - super(IssuanceError, self).__init__() + super().__init__() class ConflictError(ClientError): @@ -125,7 +119,7 @@ class ConflictError(ClientError): """ def __init__(self, location): self.location = location - super(ConflictError, self).__init__() + super().__init__() class WildcardUnsupportedError(Error): diff --git a/acme/acme/fields.py b/acme/acme/fields.py index 3b5672283..bd915e47d 100644 --- a/acme/acme/fields.py +++ b/acme/acme/fields.py @@ -12,7 +12,7 @@ class Fixed(jose.Field): def __init__(self, json_name, value): self.value = value - super(Fixed, self).__init__( + super().__init__( json_name=json_name, default=value, omitempty=False) def decode(self, value): @@ -53,7 +53,7 @@ class Resource(jose.Field): def __init__(self, resource_type, *args, **kwargs): self.resource_type = resource_type - super(Resource, self).__init__( + super().__init__( 'resource', default=resource_type, *args, **kwargs) def decode(self, value): diff --git a/acme/acme/jws.py b/acme/acme/jws.py index 7eccf0fdf..d9d3e12c3 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -50,7 +50,7 @@ class JWS(jose.JWS): # Per ACME spec, jwk and kid are mutually exclusive, so only include a # jwk field if kid is not provided. include_jwk = kid is None - return super(JWS, cls).sign(payload, key=key, alg=alg, + return super().sign(payload, key=key, alg=alg, protect=frozenset(['nonce', 'url', 'kid', 'jwk', 'alg']), nonce=nonce, url=url, kid=kid, include_jwk=include_jwk) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index c8b971b8f..36207dba0 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -132,7 +132,7 @@ class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore POSSIBLE_NAMES: Dict[str, '_Constant'] = NotImplemented def __init__(self, name): - super(_Constant, self).__init__() + super().__init__() self.POSSIBLE_NAMES[name] = self # pylint: disable=unsupported-assignment-operation self.name = name @@ -201,7 +201,7 @@ class Directory(jose.JSONDeSerializable): def __init__(self, **kwargs): kwargs = {self._internal_name(k): v for k, v in kwargs.items()} - super(Directory.Meta, self).__init__(**kwargs) + super().__init__(**kwargs) @property def terms_of_service(self): @@ -211,7 +211,7 @@ class Directory(jose.JSONDeSerializable): def __iter__(self): # When iterating over fields, use the external name 'terms_of_service' instead of # the internal '_terms_of_service'. - for name in super(Directory.Meta, self).__iter__(): + for name in super().__iter__(): yield name[1:] if name == '_terms_of_service' else name def _internal_name(self, name): @@ -357,7 +357,7 @@ class Registration(ResourceBody): if 'contact' in kwargs: # Avoid the __setattr__ used by jose.TypedJSONObjectWithFields object.__setattr__(self, '_add_contact', True) - super(Registration, self).__init__(**kwargs) + super().__init__(**kwargs) def _filter_contact(self, prefix): return tuple( @@ -383,12 +383,12 @@ class Registration(ResourceBody): def to_partial_json(self): """Modify josepy.JSONDeserializable.to_partial_json()""" - jobj = super(Registration, self).to_partial_json() + jobj = super().to_partial_json() return self._add_contact_if_appropriate(jobj) def fields_to_partial_json(self): """Modify josepy.JSONObjectWithFields.fields_to_partial_json()""" - jobj = super(Registration, self).fields_to_partial_json() + jobj = super().fields_to_partial_json() return self._add_contact_if_appropriate(jobj) @property @@ -460,19 +460,19 @@ class ChallengeBody(ResourceBody): def __init__(self, **kwargs): kwargs = {self._internal_name(k): v for k, v in kwargs.items()} - super(ChallengeBody, self).__init__(**kwargs) + super().__init__(**kwargs) def encode(self, name): - return super(ChallengeBody, self).encode(self._internal_name(name)) + return super().encode(self._internal_name(name)) def to_partial_json(self): - jobj = super(ChallengeBody, self).to_partial_json() + jobj = super().to_partial_json() jobj.update(self.chall.to_partial_json()) return jobj @classmethod def fields_from_json(cls, jobj): - jobj_fields = super(ChallengeBody, cls).fields_from_json(jobj) + jobj_fields = super().fields_from_json(jobj) jobj_fields['chall'] = challenges.Challenge.from_json(jobj) return jobj_fields @@ -487,7 +487,7 @@ class ChallengeBody(ResourceBody): def __iter__(self): # When iterating over fields, use the external name 'uri' instead of # the internal '_uri'. - for name in super(ChallengeBody, self).__iter__(): + for name in super().__iter__(): yield name[1:] if name == '_uri' else name def _internal_name(self, name): diff --git a/acme/acme/mixins.py b/acme/acme/mixins.py index 0e1e0977c..6d58e0889 100644 --- a/acme/acme/mixins.py +++ b/acme/acme/mixins.py @@ -20,7 +20,7 @@ class VersionedLEACMEMixin: # Required for @property to operate properly. See comment above. object.__setattr__(self, key, value) else: - super(VersionedLEACMEMixin, self).__setattr__(key, value) # pragma: no cover + super().__setattr__(key, value) # pragma: no cover class ResourceMixin(VersionedLEACMEMixin): @@ -30,12 +30,12 @@ class ResourceMixin(VersionedLEACMEMixin): """ def to_partial_json(self): """See josepy.JSONDeserializable.to_partial_json()""" - return _safe_jobj_compliance(super(ResourceMixin, self), + return _safe_jobj_compliance(super(), 'to_partial_json', 'resource') def fields_to_partial_json(self): """See josepy.JSONObjectWithFields.fields_to_partial_json()""" - return _safe_jobj_compliance(super(ResourceMixin, self), + return _safe_jobj_compliance(super(), 'fields_to_partial_json', 'resource') @@ -46,12 +46,12 @@ class TypeMixin(VersionedLEACMEMixin): """ def to_partial_json(self): """See josepy.JSONDeserializable.to_partial_json()""" - return _safe_jobj_compliance(super(TypeMixin, self), + return _safe_jobj_compliance(super(), 'to_partial_json', 'type') def fields_to_partial_json(self): """See josepy.JSONObjectWithFields.fields_to_partial_json()""" - return _safe_jobj_compliance(super(TypeMixin, self), + return _safe_jobj_compliance(super(), 'fields_to_partial_json', 'type') diff --git a/acme/setup.py b/acme/setup.py index 90ab5c9f3..443aab6e8 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py index 6addb09f3..89e66c6d6 100644 --- a/acme/tests/client_test.py +++ b/acme/tests/client_test.py @@ -90,7 +90,7 @@ class BackwardsCompatibleClientV2Test(ClientTestBase): """Tests for acme.client.BackwardsCompatibleClientV2.""" def setUp(self): - super(BackwardsCompatibleClientV2Test, self).setUp() + super().setUp() # contains a loaded cert self.certr = messages.CertificateResource( body=messages_test.CERT) @@ -319,7 +319,7 @@ class ClientTest(ClientTestBase): """Tests for acme.client.Client.""" def setUp(self): - super(ClientTest, self).setUp() + super().setUp() self.directory = DIRECTORY_V1 @@ -716,7 +716,7 @@ class ClientV2Test(ClientTestBase): """Tests for acme.client.ClientV2.""" def setUp(self): - super(ClientV2Test, self).setUp() + super().setUp() self.directory = DIRECTORY_V2 diff --git a/certbot-apache/certbot_apache/_internal/apacheparser.py b/certbot-apache/certbot_apache/_internal/apacheparser.py index adfbc4442..d3bd1a4bf 100644 --- a/certbot-apache/certbot_apache/_internal/apacheparser.py +++ b/certbot-apache/certbot_apache/_internal/apacheparser.py @@ -15,7 +15,7 @@ class ApacheParserNode(interfaces.ParserNode): def __init__(self, **kwargs): ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable - super(ApacheParserNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.ancestor = ancestor self.filepath = filepath self.dirty = dirty @@ -39,7 +39,7 @@ class ApacheCommentNode(ApacheParserNode): def __init__(self, **kwargs): comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable - super(ApacheCommentNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.comment = comment def __eq__(self, other): # pragma: no cover @@ -57,7 +57,7 @@ class ApacheDirectiveNode(ApacheParserNode): def __init__(self, **kwargs): name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs) - super(ApacheDirectiveNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.name = name self.parameters = parameters self.enabled = enabled @@ -83,7 +83,7 @@ class ApacheBlockNode(ApacheDirectiveNode): """ apacheconfig implementation of BlockNode interface """ def __init__(self, **kwargs): - super(ApacheBlockNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.children: Tuple[ApacheParserNode, ...] = () def __eq__(self, other): # pragma: no cover diff --git a/certbot-apache/certbot_apache/_internal/augeasparser.py b/certbot-apache/certbot_apache/_internal/augeasparser.py index 4549e935a..896e17cf8 100644 --- a/certbot-apache/certbot_apache/_internal/augeasparser.py +++ b/certbot-apache/certbot_apache/_internal/augeasparser.py @@ -80,7 +80,7 @@ class AugeasParserNode(interfaces.ParserNode): def __init__(self, **kwargs): ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable - super(AugeasParserNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.ancestor = ancestor self.filepath = filepath self.dirty = dirty @@ -169,7 +169,7 @@ class AugeasCommentNode(AugeasParserNode): def __init__(self, **kwargs): comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable - super(AugeasCommentNode, self).__init__(**kwargs) + super().__init__(**kwargs) # self.comment = comment self.comment = comment @@ -188,7 +188,7 @@ class AugeasDirectiveNode(AugeasParserNode): def __init__(self, **kwargs): name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs) - super(AugeasDirectiveNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.name = name self.enabled = enabled if parameters: @@ -245,7 +245,7 @@ class AugeasBlockNode(AugeasDirectiveNode): """ Augeas implementation of BlockNode interface """ def __init__(self, **kwargs): - super(AugeasBlockNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.children = () def __eq__(self, other): diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index 2f8665a3e..06354be30 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -8,10 +8,10 @@ import logging import re import socket import time -from typing import cast from typing import DefaultDict from typing import Dict from typing import List +from typing import Optional from typing import Set from typing import Union @@ -36,6 +36,9 @@ from certbot_apache._internal import dualparser from certbot_apache._internal import http_01 from certbot_apache._internal import obj from certbot_apache._internal import parser +from certbot_apache._internal.dualparser import DualBlockNode +from certbot_apache._internal.obj import VirtualHost +from certbot_apache._internal.parser import ApacheParser try: import apacheconfig @@ -47,6 +50,47 @@ except ImportError: # pragma: no cover logger = logging.getLogger(__name__) +class OsOptions: + """ + Dedicated class to describe the OS specificities (eg. paths, binary names) + that the Apache configurator needs to be aware to operate properly. + """ + def __init__(self, + server_root="/etc/apache2", + vhost_root="/etc/apache2/sites-available", + vhost_files="*", + logs_root="/var/log/apache2", + ctl="apache2ctl", + version_cmd: Optional[List[str]] = None, + restart_cmd: Optional[List[str]] = None, + restart_cmd_alt: Optional[List[str]] = None, + conftest_cmd: Optional[List[str]] = None, + enmod: Optional[str] = None, + dismod: Optional[str] = None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/apache2", + apache_bin: Optional[str] = None, + ): + self.server_root = server_root + self.vhost_root = vhost_root + self.vhost_files = vhost_files + self.logs_root = logs_root + self.ctl = ctl + self.version_cmd = ['apache2ctl', '-v'] if not version_cmd else version_cmd + self.restart_cmd = ['apache2ctl', 'graceful'] if not restart_cmd else restart_cmd + self.restart_cmd_alt = restart_cmd_alt + self.conftest_cmd = ['apache2ctl', 'configtest'] if not conftest_cmd else conftest_cmd + self.enmod = enmod + self.dismod = dismod + self.le_vhost_ext = le_vhost_ext + self.handle_modules = handle_modules + self.handle_sites = handle_sites + self.challenge_location = challenge_location + self.bin = apache_bin + + # TODO: Augeas sections ie. , beginning and closing # tags need to be the same case, otherwise Augeas doesn't recognize them. # This is not able to be completely remedied by regular expressions because @@ -102,27 +146,7 @@ class ApacheConfigurator(common.Installer): " change depending on the operating system Certbot is run on.)" ) - OS_DEFAULTS = dict( - server_root="/etc/apache2", - vhost_root="/etc/apache2/sites-available", - vhost_files="*", - logs_root="/var/log/apache2", - ctl="apache2ctl", - version_cmd=['apache2ctl', '-v'], - restart_cmd=['apache2ctl', 'graceful'], - conftest_cmd=['apache2ctl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, - challenge_location="/etc/apache2", - bin=None - ) - - def option(self, key): - """Get a value from options""" - return self.options.get(key) + OS_DEFAULTS = OsOptions() def pick_apache_config(self, warn_on_no_mod_ssl=True): """ @@ -152,14 +176,14 @@ class ApacheConfigurator(common.Installer): for o in opts: # Config options use dashes instead of underscores if self.conf(o.replace("_", "-")) is not None: - self.options[o] = self.conf(o.replace("_", "-")) + setattr(self.options, o, self.conf(o.replace("_", "-"))) else: - self.options[o] = self.OS_DEFAULTS[o] + setattr(self.options, o, getattr(self.OS_DEFAULTS, o)) # Special cases - cast(List[str], self.options["version_cmd"])[0] = self.option("ctl") - cast(List[str], self.options["restart_cmd"])[0] = self.option("ctl") - cast(List[str], self.options["conftest_cmd"])[0] = self.option("ctl") + self.options.version_cmd[0] = self.options.ctl + self.options.restart_cmd[0] = self.options.ctl + self.options.conftest_cmd[0] = self.options.ctl @classmethod def add_parser_arguments(cls, add): @@ -174,30 +198,30 @@ class ApacheConfigurator(common.Installer): else: # cls.OS_DEFAULTS can be distribution specific, see override classes DEFAULTS = cls.OS_DEFAULTS - add("enmod", default=DEFAULTS["enmod"], + add("enmod", default=DEFAULTS.enmod, help="Path to the Apache 'a2enmod' binary") - add("dismod", default=DEFAULTS["dismod"], + add("dismod", default=DEFAULTS.dismod, help="Path to the Apache 'a2dismod' binary") - add("le-vhost-ext", default=DEFAULTS["le_vhost_ext"], + add("le-vhost-ext", default=DEFAULTS.le_vhost_ext, help="SSL vhost configuration extension") - add("server-root", default=DEFAULTS["server_root"], + add("server-root", default=DEFAULTS.server_root, help="Apache server root directory") add("vhost-root", default=None, help="Apache server VirtualHost configuration root") - add("logs-root", default=DEFAULTS["logs_root"], + add("logs-root", default=DEFAULTS.logs_root, help="Apache server logs directory") add("challenge-location", - default=DEFAULTS["challenge_location"], + default=DEFAULTS.challenge_location, help="Directory path for challenge configuration") - add("handle-modules", default=DEFAULTS["handle_modules"], + add("handle-modules", default=DEFAULTS.handle_modules, help="Let installer handle enabling required modules for you " + "(Only Ubuntu/Debian currently)") - add("handle-sites", default=DEFAULTS["handle_sites"], + add("handle-sites", default=DEFAULTS.handle_sites, help="Let installer handle enabling sites for you " + "(Only Ubuntu/Debian currently)") - add("ctl", default=DEFAULTS["ctl"], + add("ctl", default=DEFAULTS.ctl, help="Full path to Apache control script") - add("bin", default=DEFAULTS["bin"], + add("bin", default=DEFAULTS.bin, help="Full path to apache2/httpd binary") def __init__(self, *args, **kwargs): @@ -210,7 +234,7 @@ class ApacheConfigurator(common.Installer): version = kwargs.pop("version", None) use_parsernode = kwargs.pop("use_parsernode", False) openssl_version = kwargs.pop("openssl_version", None) - super(ApacheConfigurator, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Add name_server association dict self.assoc: Dict[str, obj.VirtualHost] = {} @@ -232,11 +256,11 @@ class ApacheConfigurator(common.Installer): self.parsed_paths: List[str] = [] # These will be set in the prepare function self._prepared = False - self.parser = None - self.parser_root = None + self.parser: ApacheParser + self.parser_root: Optional[DualBlockNode] = None self.version = version self._openssl_version = openssl_version - self.vhosts = None + self.vhosts: List[VirtualHost] self.options = copy.deepcopy(self.OS_DEFAULTS) self._enhance_func = {"redirect": self._enable_redirect, "ensure-http-header": self._set_http_header, @@ -286,8 +310,8 @@ class ApacheConfigurator(common.Installer): ssl_module_location = self.parser.standard_path_from_server_root(ssl_module_location) else: # Possibility B: ssl_module is statically linked into Apache - if self.option("bin"): - ssl_module_location = self.option("bin") + if self.options.bin: + ssl_module_location = self.options.bin else: logger.warning("ssl_module is statically linked but --apache-bin is " "missing; not disabling session tickets.") @@ -317,7 +341,7 @@ class ApacheConfigurator(common.Installer): self._prepare_options() # Verify Apache is installed - self._verify_exe_availability(self.option("ctl")) + self._verify_exe_availability(self.options.ctl) # Make sure configuration is valid self.config_test() @@ -345,8 +369,9 @@ class ApacheConfigurator(common.Installer): "augeaspath": self.parser.get_root_augpath(), "ac_ast": None} if self.USE_PARSERNODE: - self.parser_root = self.get_parsernode_root(pn_meta) - self.parsed_paths = self.parser_root.parsed_paths() + parser_root = self.get_parsernode_root(pn_meta) + self.parser_root = parser_root + self.parsed_paths = parser_root.parsed_paths() # Check for errors in parsing files with Augeas self.parser.check_parsing_errors("httpd.aug") @@ -356,20 +381,20 @@ class ApacheConfigurator(common.Installer): # We may try to enable mod_ssl later. If so, we shouldn't warn if we can't find it now. # This is currently only true for debian/ubuntu. - warn_on_no_mod_ssl = not self.option("handle_modules") + warn_on_no_mod_ssl = not self.options.handle_modules self.install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest, warn_on_no_mod_ssl) # Prevent two Apache plugins from modifying a config at once try: - util.lock_dir_until_exit(self.option("server_root")) + util.lock_dir_until_exit(self.options.server_root) except (OSError, errors.LockError): logger.debug("Encountered error:", exc_info=True) raise errors.PluginError( "Unable to create a lock file in {0}. Are you running" " Certbot with sufficient privileges to modify your" - " Apache configuration?".format(self.option("server_root"))) + " Apache configuration?".format(self.options.server_root)) self._prepared = True def save(self, title=None, temporary=False): @@ -405,10 +430,10 @@ class ApacheConfigurator(common.Installer): :raises .errors.PluginError: If unable to recover the configuration """ - super(ApacheConfigurator, self).recovery_routine() + super().recovery_routine() # Reload configuration after these changes take effect if needed # ie. ApacheParser has been initialized. - if self.parser: + if hasattr(self, "parser"): # TODO: wrap into non-implementation specific parser interface self.parser.aug.load() @@ -430,7 +455,7 @@ class ApacheConfigurator(common.Installer): the function is unable to correctly revert the configuration """ - super(ApacheConfigurator, self).rollback_checkpoints(rollback) + super().rollback_checkpoints(rollback) self.parser.aug.load() def _verify_exe_availability(self, exe): @@ -444,7 +469,7 @@ class ApacheConfigurator(common.Installer): """Initializes the ApacheParser""" # If user provided vhost_root value in command line, use it return parser.ApacheParser( - self.option("server_root"), self.conf("vhost-root"), + self.options.server_root, self.conf("vhost-root"), self.version, configurator=self) def get_parsernode_root(self, metadata): @@ -452,9 +477,9 @@ class ApacheConfigurator(common.Installer): if HAS_APACHECONFIG: apache_vars = {} - apache_vars["defines"] = apache_util.parse_defines(self.option("ctl")) - apache_vars["includes"] = apache_util.parse_includes(self.option("ctl")) - apache_vars["modules"] = apache_util.parse_modules(self.option("ctl")) + apache_vars["defines"] = apache_util.parse_defines(self.options.ctl) + apache_vars["includes"] = apache_util.parse_includes(self.options.ctl) + apache_vars["modules"] = apache_util.parse_modules(self.options.ctl) metadata["apache_vars"] = apache_vars with open(self.parser.loc["root"]) as f: @@ -1051,6 +1076,9 @@ class ApacheConfigurator(common.Installer): :rtype: list """ + if not self.parser_root: + raise errors.Error("This ApacheConfigurator instance is not" # pragma: no cover + " configured to use a node parser.") vhs = [] vhosts = self.parser_root.find_blocks("VirtualHost", exclude=False) for vhblock in vhosts: @@ -1303,7 +1331,7 @@ class ApacheConfigurator(common.Installer): :param boolean temp: If the change is temporary """ - if self.option("handle_modules"): + if self.options.handle_modules: if self.version >= (2, 4) and ("socache_shmcb_module" not in self.parser.modules): self.enable_mod("socache_shmcb", temp=temp) @@ -1323,7 +1351,7 @@ class ApacheConfigurator(common.Installer): Duplicates vhost and adds default ssl options New vhost will reside as (nonssl_vhost.path) + - ``self.option("le_vhost_ext")`` + ``self.options.le_vhost_ext`` .. note:: This function saves the configuration @@ -1422,15 +1450,15 @@ class ApacheConfigurator(common.Installer): """ if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")): - fp = os.path.join(filesystem.realpath(self.option("vhost_root")), + fp = os.path.join(filesystem.realpath(self.options.vhost_root), os.path.basename(non_ssl_vh_fp)) else: # Use non-ssl filepath fp = filesystem.realpath(non_ssl_vh_fp) if fp.endswith(".conf"): - return fp[:-(len(".conf"))] + self.option("le_vhost_ext") - return fp + self.option("le_vhost_ext") + return fp[:-(len(".conf"))] + self.options.le_vhost_ext + return fp + self.options.le_vhost_ext def _sift_rewrite_rule(self, line): """Decides whether a line should be copied to a SSL vhost. @@ -2274,7 +2302,7 @@ class ApacheConfigurator(common.Installer): addr in self._get_proposed_addrs(ssl_vhost)), servername, serveralias, " ".join(rewrite_rule_args), - self.option("logs_root"))) + self.options.logs_root)) def _write_out_redirect(self, ssl_vhost, text): # This is the default name @@ -2286,7 +2314,7 @@ class ApacheConfigurator(common.Installer): if len(ssl_vhost.name) < (255 - (len(redirect_filename) + 1)): redirect_filename = "le-redirect-%s.conf" % ssl_vhost.name - redirect_filepath = os.path.join(self.option("vhost_root"), + redirect_filepath = os.path.join(self.options.vhost_root, redirect_filename) # Register the new file that will be created @@ -2406,19 +2434,18 @@ class ApacheConfigurator(common.Installer): """ try: - util.run_script(self.option("restart_cmd")) + util.run_script(self.options.restart_cmd) except errors.SubprocessError as err: logger.warning("Unable to restart apache using %s", - self.option("restart_cmd")) - alt_restart = self.option("restart_cmd_alt") + self.options.restart_cmd) + alt_restart = self.options.restart_cmd_alt if alt_restart: logger.debug("Trying alternative restart command: %s", alt_restart) # There is an alternative restart command available # This usually is "restart" verb while original is "graceful" try: - util.run_script(self.option( - "restart_cmd_alt")) + util.run_script(self.options.restart_cmd_alt) return except errors.SubprocessError as secerr: error = str(secerr) @@ -2433,7 +2460,7 @@ class ApacheConfigurator(common.Installer): """ try: - util.run_script(self.option("conftest_cmd")) + util.run_script(self.options.conftest_cmd) except errors.SubprocessError as err: raise errors.MisconfigurationError(str(err)) @@ -2449,11 +2476,11 @@ class ApacheConfigurator(common.Installer): """ try: - stdout, _ = util.run_script(self.option("version_cmd")) + stdout, _ = util.run_script(self.options.version_cmd) except errors.SubprocessError: raise errors.PluginError( "Unable to run %s -v" % - self.option("version_cmd")) + self.options.version_cmd) regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE) matches = regex.findall(stdout) diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index 66d888e97..83a1a8e08 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -47,7 +47,7 @@ class ApacheHttp01(common.ChallengePerformer): """ def __init__(self, *args, **kwargs): - super(ApacheHttp01, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.challenge_conf_pre = os.path.join( self.configurator.conf("challenge-location"), "le_http_01_challenge_pre.conf") diff --git a/certbot-apache/certbot_apache/_internal/interfaces.py b/certbot-apache/certbot_apache/_internal/interfaces.py index 38c21b785..106e2778a 100644 --- a/certbot-apache/certbot_apache/_internal/interfaces.py +++ b/certbot-apache/certbot_apache/_internal/interfaces.py @@ -238,7 +238,7 @@ class CommentNode(ParserNode, metaclass=abc.ABCMeta): created or changed after the last save. Default: False. :type dirty: bool """ - super(CommentNode, self).__init__(ancestor=kwargs['ancestor'], + super().__init__(ancestor=kwargs['ancestor'], dirty=kwargs.get('dirty', False), filepath=kwargs['filepath'], metadata=kwargs.get('metadata', {})) # pragma: no cover @@ -302,7 +302,7 @@ class DirectiveNode(ParserNode, metaclass=abc.ABCMeta): :type enabled: bool """ - super(DirectiveNode, self).__init__(ancestor=kwargs['ancestor'], + super().__init__(ancestor=kwargs['ancestor'], dirty=kwargs.get('dirty', False), filepath=kwargs['filepath'], metadata=kwargs.get('metadata', {})) # pragma: no cover diff --git a/certbot-apache/certbot_apache/_internal/obj.py b/certbot-apache/certbot_apache/_internal/obj.py index 21fd042f6..9001a860d 100644 --- a/certbot-apache/certbot_apache/_internal/obj.py +++ b/certbot-apache/certbot_apache/_internal/obj.py @@ -26,7 +26,7 @@ class Addr(common.Addr): def __hash__(self): # pylint: disable=useless-super-delegation # Python 3 requires explicit overridden for __hash__ if __eq__ or # __cmp__ is overridden. See https://bugs.python.org/issue2235 - return super(Addr, self).__hash__() + return super().__hash__() def _addr_less_specific(self, addr): """Returns if addr.get_addr() is more specific than self.get_addr().""" diff --git a/certbot-apache/certbot_apache/_internal/override_arch.py b/certbot-apache/certbot_apache/_internal/override_arch.py index 1c3aed1dc..30d161a4e 100644 --- a/certbot-apache/certbot_apache/_internal/override_arch.py +++ b/certbot-apache/certbot_apache/_internal/override_arch.py @@ -3,13 +3,14 @@ import zope.interface from certbot import interfaces from certbot_apache._internal import configurator +from certbot_apache._internal.configurator import OsOptions @zope.interface.provider(interfaces.IPluginFactory) class ArchConfigurator(configurator.ApacheConfigurator): """Arch Linux specific ApacheConfigurator override class""" - OS_DEFAULTS = dict( + OS_DEFAULTS = OsOptions( server_root="/etc/httpd", vhost_root="/etc/httpd/conf", vhost_files="*.conf", @@ -18,11 +19,5 @@ class ArchConfigurator(configurator.ApacheConfigurator): version_cmd=['apachectl', '-v'], restart_cmd=['apachectl', 'graceful'], conftest_cmd=['apachectl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, challenge_location="/etc/httpd/conf", - bin=None, ) diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py index fc77aaafc..c1a69885c 100644 --- a/certbot-apache/certbot_apache/_internal/override_centos.py +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -12,6 +12,7 @@ from certbot.errors import MisconfigurationError from certbot_apache._internal import apache_util from certbot_apache._internal import configurator from certbot_apache._internal import parser +from certbot_apache._internal.configurator import OsOptions logger = logging.getLogger(__name__) @@ -20,7 +21,7 @@ logger = logging.getLogger(__name__) class CentOSConfigurator(configurator.ApacheConfigurator): """CentOS specific ApacheConfigurator override class""" - OS_DEFAULTS = dict( + OS_DEFAULTS = OsOptions( server_root="/etc/httpd", vhost_root="/etc/httpd/conf.d", vhost_files="*.conf", @@ -30,13 +31,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): restart_cmd=['apachectl', 'graceful'], restart_cmd_alt=['apachectl', 'restart'], conftest_cmd=['apachectl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, challenge_location="/etc/httpd/conf.d", - bin=None, ) def config_test(self): @@ -51,7 +46,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): fedora = os_info[0].lower() == "fedora" try: - super(CentOSConfigurator, self).config_test() + super().config_test() except errors.MisconfigurationError: if fedora: self._try_restart_fedora() @@ -69,20 +64,22 @@ class CentOSConfigurator(configurator.ApacheConfigurator): raise errors.MisconfigurationError(str(err)) # Finish with actual config check to see if systemctl restart helped - super(CentOSConfigurator, self).config_test() + super().config_test() def _prepare_options(self): """ Override the options dictionary initialization in order to support alternative restart cmd used in CentOS. """ - super(CentOSConfigurator, self)._prepare_options() - cast(List[str], self.options["restart_cmd_alt"])[0] = self.option("ctl") + super()._prepare_options() + if not self.options.restart_cmd_alt: # pragma: no cover + raise ValueError("OS option restart_cmd_alt must be set for CentOS.") + self.options.restart_cmd_alt[0] = self.options.ctl def get_parser(self): """Initializes the ApacheParser""" return CentOSParser( - self.option("server_root"), self.option("vhost_root"), + self.options.server_root, self.options.vhost_root, self.version, configurator=self) def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -91,7 +88,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): has "LoadModule ssl_module..." before parsing the VirtualHost configuration that was created by Certbot """ - super(CentOSConfigurator, self)._deploy_cert(*args, **kwargs) + super()._deploy_cert(*args, **kwargs) if self.version < (2, 4, 0): self._deploy_loadmodule_ssl_if_needed() @@ -119,8 +116,9 @@ class CentOSConfigurator(configurator.ApacheConfigurator): else: loadmod_args = path_args - if self.parser.not_modssl_ifmodule(noarg_path): # pylint: disable=no-member - if self.parser.loc["default"] in noarg_path: + centos_parser: CentOSParser = cast(CentOSParser, self.parser) + if centos_parser.not_modssl_ifmodule(noarg_path): + if centos_parser.loc["default"] in noarg_path: # LoadModule already in the main configuration file if ("ifmodule/" in noarg_path.lower() or "ifmodule[1]" in noarg_path.lower()): @@ -168,12 +166,12 @@ class CentOSParser(parser.ApacheParser): def __init__(self, *args, **kwargs): # CentOS specific configuration file for Apache self.sysconfig_filep = "/etc/sysconfig/httpd" - super(CentOSParser, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def update_runtime_variables(self): """ Override for update_runtime_variables for custom parsing """ # Opportunistic, works if SELinux not enforced - super(CentOSParser, self).update_runtime_variables() + super().update_runtime_variables() self.parse_sysconfig_var() def parse_sysconfig_var(self): diff --git a/certbot-apache/certbot_apache/_internal/override_darwin.py b/certbot-apache/certbot_apache/_internal/override_darwin.py index 106f5fbab..e1dca7f5e 100644 --- a/certbot-apache/certbot_apache/_internal/override_darwin.py +++ b/certbot-apache/certbot_apache/_internal/override_darwin.py @@ -3,26 +3,19 @@ import zope.interface from certbot import interfaces from certbot_apache._internal import configurator +from certbot_apache._internal.configurator import OsOptions @zope.interface.provider(interfaces.IPluginFactory) class DarwinConfigurator(configurator.ApacheConfigurator): """macOS specific ApacheConfigurator override class""" - OS_DEFAULTS = dict( - server_root="/etc/apache2", + OS_DEFAULTS = OsOptions( vhost_root="/etc/apache2/other", vhost_files="*.conf", - logs_root="/var/log/apache2", ctl="apachectl", version_cmd=['apachectl', '-v'], restart_cmd=['apachectl', 'graceful'], conftest_cmd=['apachectl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, challenge_location="/etc/apache2/other", - bin=None, ) diff --git a/certbot-apache/certbot_apache/_internal/override_debian.py b/certbot-apache/certbot_apache/_internal/override_debian.py index 156d979e2..14954f095 100644 --- a/certbot-apache/certbot_apache/_internal/override_debian.py +++ b/certbot-apache/certbot_apache/_internal/override_debian.py @@ -10,6 +10,7 @@ from certbot.compat import filesystem from certbot.compat import os from certbot_apache._internal import apache_util from certbot_apache._internal import configurator +from certbot_apache._internal.configurator import OsOptions logger = logging.getLogger(__name__) @@ -18,22 +19,11 @@ logger = logging.getLogger(__name__) class DebianConfigurator(configurator.ApacheConfigurator): """Debian specific ApacheConfigurator override class""" - OS_DEFAULTS = dict( - server_root="/etc/apache2", - vhost_root="/etc/apache2/sites-available", - vhost_files="*", - logs_root="/var/log/apache2", - ctl="apache2ctl", - version_cmd=['apache2ctl', '-v'], - restart_cmd=['apache2ctl', 'graceful'], - conftest_cmd=['apache2ctl', 'configtest'], + OS_DEFAULTS = OsOptions( enmod="a2enmod", dismod="a2dismod", - le_vhost_ext="-le-ssl.conf", handle_modules=True, handle_sites=True, - challenge_location="/etc/apache2", - bin=None, ) def enable_site(self, vhost): @@ -58,7 +48,7 @@ class DebianConfigurator(configurator.ApacheConfigurator): if not os.path.isdir(os.path.dirname(enabled_path)): # For some reason, sites-enabled / sites-available do not exist # Call the parent method - return super(DebianConfigurator, self).enable_site(vhost) + return super().enable_site(vhost) self.reverter.register_file_creation(False, enabled_path) try: os.symlink(vhost.filep, enabled_path) @@ -132,11 +122,11 @@ class DebianConfigurator(configurator.ApacheConfigurator): # Generate reversal command. # Try to be safe here... check that we can probably reverse before # applying enmod command - if not util.exe_exists(self.option("dismod")): + if not util.exe_exists(self.options.dismod): raise errors.MisconfigurationError( "Unable to find a2dismod, please make sure a2enmod and " "a2dismod are configured correctly for certbot.") self.reverter.register_undo_command( - temp, [self.option("dismod"), "-f", mod_name]) - util.run_script([self.option("enmod"), mod_name]) + temp, [self.options.dismod, "-f", mod_name]) + util.run_script([self.options.enmod, mod_name]) diff --git a/certbot-apache/certbot_apache/_internal/override_fedora.py b/certbot-apache/certbot_apache/_internal/override_fedora.py index 0dc3df66b..3b947a823 100644 --- a/certbot-apache/certbot_apache/_internal/override_fedora.py +++ b/certbot-apache/certbot_apache/_internal/override_fedora.py @@ -1,7 +1,4 @@ """ Distribution specific override class for Fedora 29+ """ -from typing import cast -from typing import List - import zope.interface from certbot import errors @@ -10,13 +7,14 @@ from certbot import util from certbot_apache._internal import apache_util from certbot_apache._internal import configurator from certbot_apache._internal import parser +from certbot_apache._internal.configurator import OsOptions @zope.interface.provider(interfaces.IPluginFactory) class FedoraConfigurator(configurator.ApacheConfigurator): """Fedora 29+ specific ApacheConfigurator override class""" - OS_DEFAULTS = dict( + OS_DEFAULTS = OsOptions( server_root="/etc/httpd", vhost_root="/etc/httpd/conf.d", vhost_files="*.conf", @@ -26,13 +24,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator): restart_cmd=['apachectl', 'graceful'], restart_cmd_alt=['apachectl', 'restart'], conftest_cmd=['apachectl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, challenge_location="/etc/httpd/conf.d", - bin=None, ) def config_test(self): @@ -43,14 +35,14 @@ class FedoraConfigurator(configurator.ApacheConfigurator): during the first (re)start of httpd. """ try: - super(FedoraConfigurator, self).config_test() + super().config_test() except errors.MisconfigurationError: self._try_restart_fedora() def get_parser(self): """Initializes the ApacheParser""" return FedoraParser( - self.option("server_root"), self.option("vhost_root"), + self.options.server_root, self.options.vhost_root, self.version, configurator=self) def _try_restart_fedora(self): @@ -63,7 +55,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator): raise errors.MisconfigurationError(str(err)) # Finish with actual config check to see if systemctl restart helped - super(FedoraConfigurator, self).config_test() + super().config_test() def _prepare_options(self): """ @@ -71,10 +63,12 @@ class FedoraConfigurator(configurator.ApacheConfigurator): instead of httpd and so take advantages of this new bash script in newer versions of Fedora to restart httpd. """ - super(FedoraConfigurator, self)._prepare_options() - cast(List[str], self.options["restart_cmd"])[0] = 'apachectl' - cast(List[str], self.options["restart_cmd_alt"])[0] = 'apachectl' - cast(List[str], self.options["conftest_cmd"])[0] = 'apachectl' + super()._prepare_options() + self.options.restart_cmd[0] = 'apachectl' + if not self.options.restart_cmd_alt: # pragma: no cover + raise ValueError("OS option restart_cmd_alt must be set for Fedora.") + self.options.restart_cmd_alt[0] = 'apachectl' + self.options.conftest_cmd[0] = 'apachectl' class FedoraParser(parser.ApacheParser): @@ -82,12 +76,12 @@ class FedoraParser(parser.ApacheParser): def __init__(self, *args, **kwargs): # Fedora 29+ specific configuration file for Apache self.sysconfig_filep = "/etc/sysconfig/httpd" - super(FedoraParser, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def update_runtime_variables(self): """ Override for update_runtime_variables for custom parsing """ # Opportunistic, works if SELinux not enforced - super(FedoraParser, self).update_runtime_variables() + super().update_runtime_variables() self._parse_sysconfig_var() def _parse_sysconfig_var(self): diff --git a/certbot-apache/certbot_apache/_internal/override_gentoo.py b/certbot-apache/certbot_apache/_internal/override_gentoo.py index 773fdd568..1b86c925e 100644 --- a/certbot-apache/certbot_apache/_internal/override_gentoo.py +++ b/certbot-apache/certbot_apache/_internal/override_gentoo.py @@ -1,36 +1,23 @@ """ Distribution specific override class for Gentoo Linux """ -from typing import cast -from typing import List - import zope.interface from certbot import interfaces from certbot_apache._internal import apache_util from certbot_apache._internal import configurator from certbot_apache._internal import parser +from certbot_apache._internal.configurator import OsOptions @zope.interface.provider(interfaces.IPluginFactory) class GentooConfigurator(configurator.ApacheConfigurator): """Gentoo specific ApacheConfigurator override class""" - OS_DEFAULTS = dict( + OS_DEFAULTS = OsOptions( server_root="/etc/apache2", vhost_root="/etc/apache2/vhosts.d", vhost_files="*.conf", - logs_root="/var/log/apache2", - ctl="apache2ctl", - version_cmd=['apache2ctl', '-v'], - restart_cmd=['apache2ctl', 'graceful'], restart_cmd_alt=['apache2ctl', 'restart'], - conftest_cmd=['apache2ctl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, challenge_location="/etc/apache2/vhosts.d", - bin=None, ) def _prepare_options(self): @@ -38,13 +25,15 @@ class GentooConfigurator(configurator.ApacheConfigurator): Override the options dictionary initialization in order to support alternative restart cmd used in Gentoo. """ - super(GentooConfigurator, self)._prepare_options() - cast(List[str], self.options["restart_cmd_alt"])[0] = self.option("ctl") + super()._prepare_options() + if not self.options.restart_cmd_alt: # pragma: no cover + raise ValueError("OS option restart_cmd_alt must be set for Gentoo.") + self.options.restart_cmd_alt[0] = self.options.ctl def get_parser(self): """Initializes the ApacheParser""" return GentooParser( - self.option("server_root"), self.option("vhost_root"), + self.options.server_root, self.options.vhost_root, self.version, configurator=self) @@ -53,7 +42,7 @@ class GentooParser(parser.ApacheParser): def __init__(self, *args, **kwargs): # Gentoo specific configuration file for Apache2 self.apacheconfig_filep = "/etc/conf.d/apache2" - super(GentooParser, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def update_runtime_variables(self): """ Override for update_runtime_variables for custom parsing """ @@ -69,7 +58,7 @@ class GentooParser(parser.ApacheParser): def update_modules(self): """Get loaded modules from httpd process, and add them to DOM""" - mod_cmd = [self.configurator.option("ctl"), "modules"] + mod_cmd = [self.configurator.options.ctl, "modules"] matches = apache_util.parse_from_subprocess(mod_cmd, r"(.*)_module") for mod in matches: self.add_mod(mod.strip()) diff --git a/certbot-apache/certbot_apache/_internal/override_suse.py b/certbot-apache/certbot_apache/_internal/override_suse.py index afce98dfa..d692fd239 100644 --- a/certbot-apache/certbot_apache/_internal/override_suse.py +++ b/certbot-apache/certbot_apache/_internal/override_suse.py @@ -3,26 +3,21 @@ import zope.interface from certbot import interfaces from certbot_apache._internal import configurator +from certbot_apache._internal.configurator import OsOptions @zope.interface.provider(interfaces.IPluginFactory) class OpenSUSEConfigurator(configurator.ApacheConfigurator): """OpenSUSE specific ApacheConfigurator override class""" - OS_DEFAULTS = dict( - server_root="/etc/apache2", + OS_DEFAULTS = OsOptions( vhost_root="/etc/apache2/vhosts.d", vhost_files="*.conf", - logs_root="/var/log/apache2", ctl="apachectl", version_cmd=['apachectl', '-v'], restart_cmd=['apachectl', 'graceful'], conftest_cmd=['apachectl', 'configtest'], enmod="a2enmod", dismod="a2dismod", - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, challenge_location="/etc/apache2/vhosts.d", - bin=None, ) diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py index 1a6af4c4b..141991ccc 100644 --- a/certbot-apache/certbot_apache/_internal/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -5,12 +5,18 @@ import logging import re from typing import Dict from typing import List +from typing import Optional from certbot import errors from certbot.compat import os from certbot_apache._internal import apache_util from certbot_apache._internal import constants +try: + from augeas import Augeas +except ImportError: # pragma: no cover + Augeas = None # type: ignore + logger = logging.getLogger(__name__) @@ -39,8 +45,7 @@ class ApacheParser: self.configurator = configurator # Initialize augeas - self.aug = None - self.init_augeas() + self.aug = init_augeas() if not self.check_aug_version(): raise errors.NotSupportedError( @@ -48,7 +53,7 @@ class ApacheParser: "version 1.2.0 or higher, please make sure you have you have " "those installed.") - self.modules: Dict[str, str] = {} + self.modules: Dict[str, Optional[str]] = {} self.parser_paths: Dict[str, List[str]] = {} self.variables: Dict[str, str] = {} @@ -76,30 +81,13 @@ class ApacheParser: # Must also attempt to parse additional virtual host root if vhostroot: self.parse_file(os.path.abspath(vhostroot) + "/" + - self.configurator.option("vhost_files")) + self.configurator.options.vhost_files) # check to see if there were unparsed define statements if version < (2, 4): if self.find_dir("Define", exclude=False): raise errors.PluginError("Error parsing runtime variables") - def init_augeas(self): - """ Initialize the actual Augeas instance """ - - try: - import augeas - except ImportError: # pragma: no cover - raise errors.NoInstallationError("Problem in Augeas installation") - - self.aug = augeas.Augeas( - # specify a directory to load our preferred lens from - loadpath=constants.AUGEAS_LENS_DIR, - # Do not save backup (we do it ourselves), do not load - # anything by default - flags=(augeas.Augeas.NONE | - augeas.Augeas.NO_MODL_AUTOLOAD | - augeas.Augeas.ENABLE_SPAN)) - def check_parsing_errors(self, lens): """Verify Augeas can parse all of the lens files. @@ -294,7 +282,7 @@ class ApacheParser: def update_defines(self): """Updates the dictionary of known variables in the configuration""" - self.variables = apache_util.parse_defines(self.configurator.option("ctl")) + self.variables = apache_util.parse_defines(self.configurator.options.ctl) def update_includes(self): """Get includes from httpd process, and add them to DOM if needed""" @@ -304,7 +292,7 @@ class ApacheParser: # configuration files _ = self.find_dir("Include") - matches = apache_util.parse_includes(self.configurator.option("ctl")) + matches = apache_util.parse_includes(self.configurator.options.ctl) if matches: for i in matches: if not self.parsed_in_current(i): @@ -313,7 +301,7 @@ class ApacheParser: def update_modules(self): """Get loaded modules from httpd process, and add them to DOM""" - matches = apache_util.parse_modules(self.configurator.option("ctl")) + matches = apache_util.parse_modules(self.configurator.options.ctl) for mod in matches: self.add_mod(mod.strip()) @@ -949,3 +937,19 @@ def get_aug_path(file_path): """ return "/files%s" % file_path + + +def init_augeas() -> Augeas: + """ Initialize the actual Augeas instance """ + + if not Augeas: # pragma: no cover + raise errors.NoInstallationError("Problem in Augeas installation") + + return Augeas( + # specify a directory to load our preferred lens from + loadpath=constants.AUGEAS_LENS_DIR, + # Do not save backup (we do it ourselves), do not load + # anything by default + flags=(Augeas.NONE | + Augeas.NO_MODL_AUTOLOAD | + Augeas.ENABLE_SPAN)) diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 43165a9dd..76ebbddfd 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -1,7 +1,7 @@ from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-apache/tests/augeasnode_test.py b/certbot-apache/tests/augeasnode_test.py index 42126a5a9..85a17ab31 100644 --- a/certbot-apache/tests/augeasnode_test.py +++ b/certbot-apache/tests/augeasnode_test.py @@ -29,7 +29,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- """Test AugeasParserNode using available test configurations""" def setUp(self): # pylint: disable=arguments-differ - super(AugeasParserNodeTest, self).setUp() + super().setUp() with mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.get_parsernode_root") as mock_parsernode: mock_parsernode.side_effect = _get_augeasnode_mock( diff --git a/certbot-apache/tests/autohsts_test.py b/certbot-apache/tests/autohsts_test.py index f09e02fc7..72d26a33a 100644 --- a/certbot-apache/tests/autohsts_test.py +++ b/certbot-apache/tests/autohsts_test.py @@ -18,7 +18,7 @@ class AutoHSTSTest(util.ApacheTest): # pylint: disable=protected-access def setUp(self): # pylint: disable=arguments-differ - super(AutoHSTSTest, self).setUp() + super().setUp() self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir) diff --git a/certbot-apache/tests/centos6_test.py b/certbot-apache/tests/centos6_test.py index 27b4f8e80..bfbe22ad0 100644 --- a/certbot-apache/tests/centos6_test.py +++ b/certbot-apache/tests/centos6_test.py @@ -36,9 +36,9 @@ class CentOS6Tests(util.ApacheTest): test_dir = "centos6_apache/apache" config_root = "centos6_apache/apache/httpd" vhost_root = "centos6_apache/apache/httpd/conf.d" - super(CentOS6Tests, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) + super().setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, diff --git a/certbot-apache/tests/centos_test.py b/certbot-apache/tests/centos_test.py index b7e9c1cb6..a92c37979 100644 --- a/certbot-apache/tests/centos_test.py +++ b/certbot-apache/tests/centos_test.py @@ -41,9 +41,9 @@ class FedoraRestartTest(util.ApacheTest): test_dir = "centos7_apache/apache" config_root = "centos7_apache/apache/httpd" vhost_root = "centos7_apache/apache/httpd/conf.d" - super(FedoraRestartTest, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) + super().setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, os_info="fedora_old") @@ -96,9 +96,9 @@ class MultipleVhostsTestCentOS(util.ApacheTest): test_dir = "centos7_apache/apache" config_root = "centos7_apache/apache/httpd" vhost_root = "centos7_apache/apache/httpd/conf.d" - super(MultipleVhostsTestCentOS, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) + super().setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, diff --git a/certbot-apache/tests/complex_parsing_test.py b/certbot-apache/tests/complex_parsing_test.py index 8b795b0b6..e36bd85d1 100644 --- a/certbot-apache/tests/complex_parsing_test.py +++ b/certbot-apache/tests/complex_parsing_test.py @@ -11,7 +11,7 @@ class ComplexParserTest(util.ParserTest): """Apache Parser Test.""" def setUp(self): # pylint: disable=arguments-differ - super(ComplexParserTest, self).setUp( + super().setUp( "complex_parsing", "complex_parsing") self.setup_variables() diff --git a/certbot-apache/tests/configurator_reverter_test.py b/certbot-apache/tests/configurator_reverter_test.py index 8596195d8..d8f5ddd05 100644 --- a/certbot-apache/tests/configurator_reverter_test.py +++ b/certbot-apache/tests/configurator_reverter_test.py @@ -16,7 +16,7 @@ class ConfiguratorReverterTest(util.ApacheTest): def setUp(self): # pylint: disable=arguments-differ - super(ConfiguratorReverterTest, self).setUp() + super().setUp() self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir) diff --git a/certbot-apache/tests/configurator_test.py b/certbot-apache/tests/configurator_test.py index ccf26a8b2..20aa72346 100644 --- a/certbot-apache/tests/configurator_test.py +++ b/certbot-apache/tests/configurator_test.py @@ -30,7 +30,7 @@ class MultipleVhostsTest(util.ApacheTest): """Test two standard well-configured HTTP vhosts.""" def setUp(self): # pylint: disable=arguments-differ - super(MultipleVhostsTest, self).setUp() + super().setUp() self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir) @@ -103,9 +103,9 @@ class MultipleVhostsTest(util.ApacheTest): "handle_modules", "handle_sites", "ctl"] exp = {} - for k in ApacheConfigurator.OS_DEFAULTS: + for k in ApacheConfigurator.OS_DEFAULTS.__dict__.keys(): if k in parserargs: - exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k] + exp[k.replace("_", "-")] = getattr(ApacheConfigurator.OS_DEFAULTS, k) # Special cases exp["vhost-root"] = None @@ -128,14 +128,13 @@ class MultipleVhostsTest(util.ApacheTest): def test_all_configurators_defaults_defined(self): from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES from certbot_apache._internal.configurator import ApacheConfigurator - parameters = set(ApacheConfigurator.OS_DEFAULTS.keys()) + parameters = set(ApacheConfigurator.OS_DEFAULTS.__dict__.keys()) for cls in OVERRIDE_CLASSES.values(): - self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.keys()))) + self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.__dict__.keys()))) def test_constant(self): self.assertTrue("debian_apache_2_4/multiple_vhosts/apache" in - self.config.option("server_root")) - self.assertEqual(self.config.option("nonexistent"), None) + self.config.options.server_root) @certbot_util.patch_get_utility() def test_get_all_names(self, mock_getutility): @@ -1477,9 +1476,9 @@ class AugeasVhostsTest(util.ApacheTest): td = "debian_apache_2_4/augeas_vhosts" cr = "debian_apache_2_4/augeas_vhosts/apache2" vr = "debian_apache_2_4/augeas_vhosts/apache2/sites-available" - super(AugeasVhostsTest, self).setUp(test_dir=td, - config_root=cr, - vhost_root=vr) + super().setUp(test_dir=td, + config_root=cr, + vhost_root=vr) self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, @@ -1556,9 +1555,9 @@ class MultiVhostsTest(util.ApacheTest): td = "debian_apache_2_4/multi_vhosts" cr = "debian_apache_2_4/multi_vhosts/apache2" vr = "debian_apache_2_4/multi_vhosts/apache2/sites-available" - super(MultiVhostsTest, self).setUp(test_dir=td, - config_root=cr, - vhost_root=vr) + super().setUp(test_dir=td, + config_root=cr, + vhost_root=vr) self.config = util.get_apache_configurator( self.config_path, self.vhost_path, @@ -1661,7 +1660,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): """Test that the options-ssl-nginx.conf file is installed and updated properly.""" def setUp(self): # pylint: disable=arguments-differ - super(InstallSslOptionsConfTest, self).setUp() + super().setUp() self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir) @@ -1774,7 +1773,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): # ssl_module statically linked self.config._openssl_version = None self.config.parser.modules['ssl_module'] = None - self.config.options['bin'] = '/fake/path/to/httpd' + self.config.options.bin = '/fake/path/to/httpd' with mock.patch("certbot_apache._internal.configurator." "ApacheConfigurator._open_module_file") as mock_omf: mock_omf.return_value = some_string_contents @@ -1810,7 +1809,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): # When ssl_module is statically linked but --apache-bin not provided self.config._openssl_version = None - self.config.options['bin'] = None + self.config.options.bin = None self.config.parser.modules['ssl_module'] = None with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: self.assertEqual(self.config.openssl_version(), None) diff --git a/certbot-apache/tests/debian_test.py b/certbot-apache/tests/debian_test.py index 192e3cba5..c72b8b6ae 100644 --- a/certbot-apache/tests/debian_test.py +++ b/certbot-apache/tests/debian_test.py @@ -20,7 +20,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): _multiprocess_can_split_ = True def setUp(self): # pylint: disable=arguments-differ - super(MultipleVhostsTestDebian, self).setUp() + super().setUp() self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, os_info="debian") diff --git a/certbot-apache/tests/fedora_test.py b/certbot-apache/tests/fedora_test.py index 50831802b..d48559cee 100644 --- a/certbot-apache/tests/fedora_test.py +++ b/certbot-apache/tests/fedora_test.py @@ -46,9 +46,9 @@ class FedoraRestartTest(util.ApacheTest): test_dir = "centos7_apache/apache" config_root = "centos7_apache/apache/httpd" vhost_root = "centos7_apache/apache/httpd/conf.d" - super(FedoraRestartTest, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) + super().setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, os_info="fedora") @@ -90,9 +90,9 @@ class MultipleVhostsTestFedora(util.ApacheTest): test_dir = "centos7_apache/apache" config_root = "centos7_apache/apache/httpd" vhost_root = "centos7_apache/apache/httpd/conf.d" - super(MultipleVhostsTestFedora, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) + super().setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, diff --git a/certbot-apache/tests/gentoo_test.py b/certbot-apache/tests/gentoo_test.py index 64f7d1062..cf39ff5cb 100644 --- a/certbot-apache/tests/gentoo_test.py +++ b/certbot-apache/tests/gentoo_test.py @@ -50,9 +50,9 @@ class MultipleVhostsTestGentoo(util.ApacheTest): test_dir = "gentoo_apache/apache" config_root = "gentoo_apache/apache/apache2" vhost_root = "gentoo_apache/apache/apache2/vhosts.d" - super(MultipleVhostsTestGentoo, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) + super().setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) # pylint: disable=line-too-long with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_runtime_variables"): diff --git a/certbot-apache/tests/http_01_test.py b/certbot-apache/tests/http_01_test.py index 9d66c3aa0..71f2db500 100644 --- a/certbot-apache/tests/http_01_test.py +++ b/certbot-apache/tests/http_01_test.py @@ -24,7 +24,7 @@ class ApacheHttp01Test(util.ApacheTest): """Test for certbot_apache._internal.http_01.ApacheHttp01.""" def setUp(self, *args, **kwargs): # pylint: disable=arguments-differ - super(ApacheHttp01Test, self).setUp(*args, **kwargs) + super().setUp(*args, **kwargs) self.account_key = self.rsa512jwk self.achalls: List[achallenges.KeyAuthorizationAnnotatedChallenge] = [] diff --git a/certbot-apache/tests/parser_test.py b/certbot-apache/tests/parser_test.py index 7aedec31d..e30eca153 100644 --- a/certbot-apache/tests/parser_test.py +++ b/certbot-apache/tests/parser_test.py @@ -16,7 +16,7 @@ class BasicParserTest(util.ParserTest): """Apache Parser Test.""" def setUp(self): # pylint: disable=arguments-differ - super(BasicParserTest, self).setUp() + super().setUp() def tearDown(self): shutil.rmtree(self.temp_dir) @@ -305,11 +305,9 @@ class BasicParserTest(util.ParserTest): self.assertRaises( errors.PluginError, self.parser.update_runtime_variables) - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.option") @mock.patch("certbot_apache._internal.apache_util.subprocess.Popen") - def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt): + def test_update_runtime_vars_bad_ctl(self, mock_popen): mock_popen.side_effect = OSError - mock_opt.return_value = "nonexistent" self.assertRaises( errors.MisconfigurationError, self.parser.update_runtime_variables) @@ -332,14 +330,14 @@ class BasicParserTest(util.ParserTest): class ParserInitTest(util.ApacheTest): def setUp(self): # pylint: disable=arguments-differ - super(ParserInitTest, self).setUp() + super().setUp() def tearDown(self): shutil.rmtree(self.temp_dir) shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) - @mock.patch("certbot_apache._internal.parser.ApacheParser.init_augeas") + @mock.patch("certbot_apache._internal.parser.init_augeas") def test_prepare_no_augeas(self, mock_init_augeas): from certbot_apache._internal.parser import ApacheParser mock_init_augeas.side_effect = errors.NoInstallationError diff --git a/certbot-apache/tests/parsernode_configurator_test.py b/certbot-apache/tests/parsernode_configurator_test.py index 7fbec2540..411871a43 100644 --- a/certbot-apache/tests/parsernode_configurator_test.py +++ b/certbot-apache/tests/parsernode_configurator_test.py @@ -20,7 +20,7 @@ class ConfiguratorParserNodeTest(util.ApacheTest): # pylint: disable=too-many-p """Test AugeasParserNode using available test configurations""" def setUp(self): # pylint: disable=arguments-differ - super(ConfiguratorParserNodeTest, self).setUp() + super().setUp() self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, diff --git a/certbot-apache/tests/parsernode_test.py b/certbot-apache/tests/parsernode_test.py index a86952f53..4ea5f8415 100644 --- a/certbot-apache/tests/parsernode_test.py +++ b/certbot-apache/tests/parsernode_test.py @@ -18,7 +18,7 @@ class DummyParserNode(interfaces.ParserNode): self.dirty = dirty self.filepath = filepath self.metadata = metadata - super(DummyParserNode, self).__init__(**kwargs) + super().__init__(**kwargs) def save(self, msg): # pragma: no cover """Save""" @@ -38,7 +38,7 @@ class DummyCommentNode(DummyParserNode): """ comment, kwargs = util.commentnode_kwargs(kwargs) self.comment = comment - super(DummyCommentNode, self).__init__(**kwargs) + super().__init__(**kwargs) class DummyDirectiveNode(DummyParserNode): @@ -54,7 +54,7 @@ class DummyDirectiveNode(DummyParserNode): self.parameters = parameters self.enabled = enabled - super(DummyDirectiveNode, self).__init__(**kwargs) + super().__init__(**kwargs) def set_parameters(self, parameters): # pragma: no cover """Set parameters""" diff --git a/certbot-apache/tests/util.py b/certbot-apache/tests/util.py index 18c7e5aca..a0b44d188 100644 --- a/certbot-apache/tests/util.py +++ b/certbot-apache/tests/util.py @@ -67,7 +67,7 @@ class ParserTest(ApacheTest): def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts", config_root="debian_apache_2_4/multiple_vhosts/apache2", vhost_root="debian_apache_2_4/multiple_vhosts/apache2/sites-available"): - super(ParserTest, self).setUp(test_dir, config_root, vhost_root) + super().setUp(test_dir, config_root, vhost_root) zope.component.provideUtility(display_util.FileDisplay(sys.stdout, False)) @@ -123,11 +123,11 @@ def get_apache_configurator( version=version, use_parsernode=use_parsernode, openssl_version=openssl_version) if not conf_vhost_path: - config_class.OS_DEFAULTS["vhost_root"] = vhost_path + config_class.OS_DEFAULTS.vhost_root = vhost_path else: # Custom virtualhost path was requested config.config.apache_vhost_root = conf_vhost_path - config.config.apache_ctl = config_class.OS_DEFAULTS["ctl"] + config.config.apache_ctl = config_class.OS_DEFAULTS.ctl config.prepare() return config diff --git a/certbot-auto b/certbot-auto index 24af0b4c3..c37c45596 100755 --- a/certbot-auto +++ b/certbot-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.13.0" +LE_AUTO_VERSION="1.14.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -800,6 +800,7 @@ BootstrapMageiaCommon() { # packages BOOTSTRAP_VERSION is not set. if [ -f /etc/debian_version ]; then DEPRECATED_OS=1 + NO_SELF_UPGRADE=1 elif [ -f /etc/mageia-release ]; then # Mageia has both /etc/mageia-release and /etc/redhat-release DEPRECATED_OS=1 @@ -1488,18 +1489,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.13.0 \ - --hash=sha256:082eb732e1318bb9605afa7aea8db2c2f4c5029d523c73f24c6aa98f03caff76 \ - --hash=sha256:64cf41b57df7667d9d849fcaa9031a4f151788246733d1f4c3f37a5aa5e2f458 -acme==1.13.0 \ - --hash=sha256:93b6365c9425de03497a6b8aee1107814501d2974499b42e9bcc9a7378771143 \ - --hash=sha256:6b4257dfd6a6d5f01e8cd4f0b10422c17836bed7c67e9c5b0a0ad6c7d651c088 -certbot-apache==1.13.0 \ - --hash=sha256:36ed02ac7d2d91febee8dd3181ae9095b3f06434c9ed8959fbc6db24ab4da2e8 \ - --hash=sha256:4b5a16e80c1418e2edc05fc2578f522fb24974b2c13eb747cdfeef69e5bd5ae1 -certbot-nginx==1.13.0 \ - --hash=sha256:3ff271f65321b25c77a868af21f76f58754a7d61529ad565a1d66e29c711120f \ - --hash=sha256:9e972cc19c0fa9e5b7863da0423b156fbfb5623fd30b558fd2fd6d21c24c0b08 +certbot==1.14.0 \ + --hash=sha256:67b4d26ceaea6c7f8325d0d45169e7a165a2cabc7122c84bc971ba068ca19cca \ + --hash=sha256:959ea90c6bb8dca38eab9772722cb940972ef6afcd5f15deef08b3c3636841eb +acme==1.14.0 \ + --hash=sha256:4f48c41261202f1a389ec2986b2580b58f53e0d5a1ae2463b34318d78b87fc66 \ + --hash=sha256:61daccfb0343628cbbca551a7fc4c82482113952c21db3fe0c585b7c98fa1c35 +certbot-apache==1.14.0 \ + --hash=sha256:b757038db23db707c44630fecb46e99172bd791f0db5a8e623c0842613c4d3d9 \ + --hash=sha256:887fe4a21af2de1e5c2c9428bacba6eb7c1219257bc70f1a1d8447c8a321adb0 +certbot-nginx==1.14.0 \ + --hash=sha256:8916a815437988d6c192df9f035bb7a176eab20eee0956677b335d0698d243fb \ + --hash=sha256:cc2a8a0de56d9bb6b2efbda6c80c647dad8db2bb90675cac03ade94bd5fc8597 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/context.py b/certbot-ci/certbot_integration_tests/nginx_tests/context.py index 6f0f833a0..3ee1766a1 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/context.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/context.py @@ -11,7 +11,7 @@ from certbot_integration_tests.utils import misc class IntegrationTestsContext(certbot_context.IntegrationTestsContext): """General fixture describing a certbot-nginx integration tests context""" def __init__(self, request): - super(IntegrationTestsContext, self).__init__(request) + super().__init__(request) self.nginx_root = os.path.join(self.workspace, 'nginx') os.mkdir(self.nginx_root) @@ -29,7 +29,7 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext): def cleanup(self): self._stop_nginx() - super(IntegrationTestsContext, self).cleanup() + super().cleanup() def certbot_test_nginx(self, args): """ diff --git a/certbot-ci/certbot_integration_tests/rfc2136_tests/context.py b/certbot-ci/certbot_integration_tests/rfc2136_tests/context.py index 8df89f473..3d4147313 100644 --- a/certbot-ci/certbot_integration_tests/rfc2136_tests/context.py +++ b/certbot-ci/certbot_integration_tests/rfc2136_tests/context.py @@ -13,11 +13,10 @@ from certbot_integration_tests.utils import certbot_call class IntegrationTestsContext(certbot_context.IntegrationTestsContext): """Integration test context for certbot-dns-rfc2136""" def __init__(self, request): - super(IntegrationTestsContext, self).__init__(request) + super().__init__(request) self.request = request - self._dns_xdist = None if hasattr(request.config, 'workerinput'): # Worker node self._dns_xdist = request.config.workerinput['dns_xdist'] else: # Primary node @@ -45,7 +44,6 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext): src_file = resource_filename('certbot_integration_tests', 'assets/bind-config/rfc2136-credentials-{}.ini.tpl' .format(label)) - contents = None with open(src_file, 'r') as f: contents = f.read().format( diff --git a/certbot-ci/certbot_integration_tests/utils/dns_server.py b/certbot-ci/certbot_integration_tests/utils/dns_server.py index 31e7aee18..48f5a533e 100644 --- a/certbot-ci/certbot_integration_tests/utils/dns_server.py +++ b/certbot-ci/certbot_integration_tests/utils/dns_server.py @@ -8,6 +8,7 @@ import subprocess import sys import tempfile import time +from typing import Optional from pkg_resources import resource_filename @@ -38,7 +39,7 @@ class DNSServer: self.bind_root = tempfile.mkdtemp() - self.process: subprocess.Popen = None + self.process: Optional[subprocess.Popen] = None self.dns_xdist = {"address": BIND_BIND_ADDRESS[0], "port": BIND_BIND_ADDRESS[1]} @@ -119,6 +120,9 @@ class DNSServer: but otherwise the contents are ignored. :param int attempts: The number of attempts to make. """ + if not self.process: + raise ValueError("DNS server has not been started. Please run start() first.") + for _ in range(attempts): if self.process.poll(): raise ValueError("BIND9 server stopped unexpectedly") diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py index 2b3f94581..909a9d37c 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py @@ -22,7 +22,7 @@ class Proxy(configurators_common.Proxy): def __init__(self, args): """Initializes the plugin with the given command line args""" - super(Proxy, self).__init__(args) + super().__init__(args) self.le_config.apache_le_vhost_ext = "-le-ssl.conf" self.modules = self.server_root = self.test_conf = self.version = None @@ -34,7 +34,7 @@ class Proxy(configurators_common.Proxy): def load_config(self): """Loads the next configuration for the plugin to test""" - config = super(Proxy, self).load_config() + config = super().load_config() self._all_names, self._test_names = _get_names(config) server_root = _get_server_root(config) @@ -54,9 +54,9 @@ class Proxy(configurators_common.Proxy): def _prepare_configurator(self): """Prepares the Apache plugin for testing""" - for k in entrypoint.ENTRYPOINT.OS_DEFAULTS: + for k in entrypoint.ENTRYPOINT.OS_DEFAULTS.__dict__.keys(): setattr(self.le_config, "apache_" + k, - entrypoint.ENTRYPOINT.OS_DEFAULTS[k]) + getattr(entrypoint.ENTRYPOINT.OS_DEFAULTS, k)) self._configurator = entrypoint.ENTRYPOINT( config=configuration.NamespaceConfig(self.le_config), @@ -65,7 +65,7 @@ class Proxy(configurators_common.Proxy): def cleanup_from_tests(self): """Performs any necessary cleanup from running plugin tests""" - super(Proxy, self).cleanup_from_tests() + super().cleanup_from_tests() mock.patch.stopall() diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index bf768f8f8..bf26291ea 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -33,7 +33,9 @@ class Proxy: self.args = args self.http_port = 80 self.https_port = 443 - self._configurator = self._all_names = self._test_names = None + self._configurator = None + self._all_names = None + self._test_names = None def __getattr__(self, name): """Wraps the configurator methods""" @@ -93,5 +95,7 @@ class Proxy: """Installs cert""" cert_path, key_path, chain_path = self.copy_certs_and_keys( cert_path, key_path, chain_path) + if not self._configurator: + raise ValueError("Configurator plugin is not set.") self._configurator.deploy_cert( domain, cert_path, key_path, chain_path, fullchain_path) diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index d7b7a120e..e209480e3 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -21,7 +21,7 @@ class Proxy(configurators_common.Proxy): def load_config(self): """Loads the next configuration for the plugin to test""" - config = super(Proxy, self).load_config() + config = super().load_config() self._all_names, self._test_names = _get_names(config) server_root = _get_server_root(config) diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 3e9e6a7c2..f1bf28596 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py index 034d7bbb2..8f42b3ce9 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py @@ -3,6 +3,7 @@ import logging from typing import Any from typing import Dict from typing import List +from typing import Optional import CloudFlare import zope.interface @@ -10,6 +11,7 @@ import zope.interface from certbot import errors from certbot import interfaces from certbot.plugins import dns_common +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -29,12 +31,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 120 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add) + super().add_parser_arguments(add) add('credentials', help='Cloudflare credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -79,6 +81,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_cloudflare_client().del_txt_record(domain, validation_name, validation) def _get_cloudflare_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") if self.credentials.conf('api-token'): return _CloudflareClient(None, self.credentials.conf('api-token')) return _CloudflareClient(self.credentials.conf('email'), self.credentials.conf('api-key')) diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 2e5215f9c..fcc7c3036 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py index 6aaebde3b..94676f9d2 100644 --- a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py +++ b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py @@ -27,7 +27,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic def setUp(self): from certbot_dns_cloudflare._internal.dns_cloudflare import Authenticator - super(AuthenticatorTest, self).setUp() + super().setUp() path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"cloudflare_email": EMAIL, "cloudflare_api_key": API_KEY}, path) diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py index 654c04c70..1ec5cb5ab 100644 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py @@ -1,5 +1,6 @@ """DNS Authenticator for CloudXNS DNS.""" import logging +from typing import Optional from lexicon.providers import cloudxns import zope.interface @@ -8,6 +9,7 @@ from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -26,12 +28,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 60 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + super().add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='CloudXNS credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -56,6 +58,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_cloudxns_client().del_txt_record(domain, validation_name, validation) def _get_cloudxns_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _CloudXNSLexiconClient(self.credentials.conf('api-key'), self.credentials.conf('secret-key'), self.ttl) @@ -67,7 +71,7 @@ class _CloudXNSLexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, api_key, secret_key, ttl): - super(_CloudXNSLexiconClient, self).__init__() + super().__init__() config = dns_common_lexicon.build_lexicon_config('cloudxns', { 'ttl': ttl, diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 44bada6bc..6452d325d 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/tests/dns_cloudxns_test.py b/certbot-dns-cloudxns/tests/dns_cloudxns_test.py index 43c69790f..81dea5ca4 100644 --- a/certbot-dns-cloudxns/tests/dns_cloudxns_test.py +++ b/certbot-dns-cloudxns/tests/dns_cloudxns_test.py @@ -26,7 +26,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_cloudxns._internal.dns_cloudxns import Authenticator diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py index cb5012fb7..23b669847 100644 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py @@ -1,5 +1,6 @@ """DNS Authenticator for DigitalOcean.""" import logging +from typing import Optional import digitalocean import zope.interface @@ -7,6 +8,7 @@ import zope.interface from certbot import errors from certbot import interfaces from certbot.plugins import dns_common +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -24,12 +26,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 30 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add) + super().add_parser_arguments(add) add('credentials', help='DigitalOcean credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -53,6 +55,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_digitalocean_client().del_txt_record(domain, validation_name, validation) def _get_digitalocean_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _DigitalOceanClient(self.credentials.conf('token')) diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 106eb3cac..33f16314e 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/tests/dns_digitalocean_test.py b/certbot-dns-digitalocean/tests/dns_digitalocean_test.py index 0dfa2fd59..07972bdde 100644 --- a/certbot-dns-digitalocean/tests/dns_digitalocean_test.py +++ b/certbot-dns-digitalocean/tests/dns_digitalocean_test.py @@ -23,7 +23,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic def setUp(self): from certbot_dns_digitalocean._internal.dns_digitalocean import Authenticator - super(AuthenticatorTest, self).setUp() + super().setUp() path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"digitalocean_token": TOKEN}, path) diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py index 9f7f100d7..858ee8925 100644 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py @@ -1,5 +1,6 @@ """DNS Authenticator for DNSimple DNS.""" import logging +from typing import Optional from lexicon.providers import dnsimple import zope.interface @@ -8,6 +9,7 @@ from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -26,12 +28,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 60 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + super().add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='DNSimple credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -54,6 +56,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_dnsimple_client().del_txt_record(domain, validation_name, validation) def _get_dnsimple_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _DNSimpleLexiconClient(self.credentials.conf('token'), self.ttl) @@ -63,7 +67,7 @@ class _DNSimpleLexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, token, ttl): - super(_DNSimpleLexiconClient, self).__init__() + super().__init__() config = dns_common_lexicon.build_lexicon_config('dnssimple', { 'ttl': ttl, diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 99a3c92a3..14fcd4a69 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/tests/dns_dnsimple_test.py b/certbot-dns-dnsimple/tests/dns_dnsimple_test.py index 40eba4754..fc3dc5b1f 100644 --- a/certbot-dns-dnsimple/tests/dns_dnsimple_test.py +++ b/certbot-dns-dnsimple/tests/dns_dnsimple_test.py @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_dnsimple._internal.dns_dnsimple import Authenticator diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py index 4a1fcffc3..67903e19d 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py @@ -1,5 +1,6 @@ """DNS Authenticator for DNS Made Easy DNS.""" import logging +from typing import Optional from lexicon.providers import dnsmadeeasy import zope.interface @@ -8,6 +9,7 @@ from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -27,12 +29,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 60 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) + super().add_parser_arguments(add, default_propagation_seconds=60) add('credentials', help='DNS Made Easy credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -58,6 +60,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_dnsmadeeasy_client().del_txt_record(domain, validation_name, validation) def _get_dnsmadeeasy_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _DNSMadeEasyLexiconClient(self.credentials.conf('api-key'), self.credentials.conf('secret-key'), self.ttl) @@ -69,7 +73,7 @@ class _DNSMadeEasyLexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, api_key, secret_key, ttl): - super(_DNSMadeEasyLexiconClient, self).__init__() + super().__init__() config = dns_common_lexicon.build_lexicon_config('dnsmadeeasy', { 'ttl': ttl, diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index b126b4e8f..dd02d96b5 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py b/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py index 4a69e977c..a04716d95 100644 --- a/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py +++ b/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py @@ -22,7 +22,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import Authenticator diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py index 39deddae5..57ff01671 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py @@ -1,12 +1,15 @@ """DNS Authenticator for Gehirn Infrastructure Service DNS.""" import logging +from typing import Optional from lexicon.providers import gehirn import zope.interface +from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -26,12 +29,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 60 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + super().add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='Gehirn Infrastructure Service credentials file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -57,6 +60,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_gehirn_client().del_txt_record(domain, validation_name, validation) def _get_gehirn_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _GehirnLexiconClient( self.credentials.conf('api-token'), self.credentials.conf('api-secret'), @@ -70,7 +75,7 @@ class _GehirnLexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, api_token, api_secret, ttl): - super(_GehirnLexiconClient, self).__init__() + super().__init__() config = dns_common_lexicon.build_lexicon_config('gehirn', { 'ttl': ttl, @@ -84,4 +89,4 @@ class _GehirnLexiconClient(dns_common_lexicon.LexiconClient): def _handle_http_error(self, e, domain_name): if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')): return None # Expected errors when zone name guess is wrong - return super(_GehirnLexiconClient, self)._handle_http_error(e, domain_name) + return super()._handle_http_error(e, domain_name) diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index cada7a85e..00ff0834a 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-gehirn/tests/dns_gehirn_test.py b/certbot-dns-gehirn/tests/dns_gehirn_test.py index 0598a5eb5..1310f74ca 100644 --- a/certbot-dns-gehirn/tests/dns_gehirn_test.py +++ b/certbot-dns-gehirn/tests/dns_gehirn_test.py @@ -21,7 +21,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_gehirn._internal.dns_gehirn import Authenticator diff --git a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py index 363c5e079..3a2686a63 100644 --- a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py +++ b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py @@ -32,13 +32,9 @@ class Authenticator(dns_common.DNSAuthenticator): 'for DNS).') ttl = 60 - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) + super().add_parser_arguments(add, default_propagation_seconds=60) add('credentials', help=('Path to Google Cloud DNS service account JSON file. (See {0} for' + 'information about creating a service account and {1} for information about the' + diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index e8b26eceb..4933ffb0b 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-google/tests/dns_google_test.py b/certbot-dns-google/tests/dns_google_test.py index 4f71db551..83fa29b41 100644 --- a/certbot-dns-google/tests/dns_google_test.py +++ b/certbot-dns-google/tests/dns_google_test.py @@ -26,14 +26,14 @@ PROJECT_ID = "test-test-1" class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_google._internal.dns_google import Authenticator path = os.path.join(self.tempdir, 'file.json') open(path, "wb").close() - super(AuthenticatorTest, self).setUp() + super().setUp() self.config = mock.MagicMock(google_credentials=path, google_propagation_seconds=0) # don't wait during tests 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 c1b5e066f..b1649cf61 100644 --- a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py +++ b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py @@ -1,6 +1,7 @@ """DNS Authenticator for Linode.""" import logging import re +from typing import Optional from lexicon.providers import linode from lexicon.providers import linode4 @@ -10,6 +11,7 @@ from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -27,12 +29,12 @@ class Authenticator(dns_common.DNSAuthenticator): description = 'Obtain certificates using a DNS TXT record (if you are using Linode for DNS).' def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=120) + super().add_parser_arguments(add, default_propagation_seconds=120) add('credentials', help='Linode credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -56,6 +58,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_linode_client().del_txt_record(domain, validation_name, validation) def _get_linode_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") api_key = self.credentials.conf('key') api_version = self.credentials.conf('version') if api_version == '': @@ -81,7 +85,7 @@ class _LinodeLexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, api_key, api_version): - super(_LinodeLexiconClient, self).__init__() + super().__init__() self.api_version = api_version diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 63da31df2..55dad1d60 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-linode/tests/dns_linode_test.py b/certbot-dns-linode/tests/dns_linode_test.py index fb9b1aa93..9861d2ab0 100644 --- a/certbot-dns-linode/tests/dns_linode_test.py +++ b/certbot-dns-linode/tests/dns_linode_test.py @@ -22,7 +22,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"linode_key": TOKEN}, path) diff --git a/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py index d5b499c72..ed90b63d9 100644 --- a/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py +++ b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py @@ -1,5 +1,6 @@ """DNS Authenticator for LuaDNS DNS.""" import logging +from typing import Optional from lexicon.providers import luadns import zope.interface @@ -8,6 +9,7 @@ from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -26,12 +28,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 60 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + super().add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='LuaDNS credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -55,6 +57,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_luadns_client().del_txt_record(domain, validation_name, validation) def _get_luadns_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _LuaDNSLexiconClient(self.credentials.conf('email'), self.credentials.conf('token'), self.ttl) @@ -66,7 +70,7 @@ class _LuaDNSLexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, email, token, ttl): - super(_LuaDNSLexiconClient, self).__init__() + super().__init__() config = dns_common_lexicon.build_lexicon_config('luadns', { 'ttl': ttl, diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 61b9dfa78..46c71c46b 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-luadns/tests/dns_luadns_test.py b/certbot-dns-luadns/tests/dns_luadns_test.py index a1242582f..7592e2323 100644 --- a/certbot-dns-luadns/tests/dns_luadns_test.py +++ b/certbot-dns-luadns/tests/dns_luadns_test.py @@ -21,7 +21,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_luadns._internal.dns_luadns import Authenticator diff --git a/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py index d328d80ce..ce46ad835 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py +++ b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py @@ -1,5 +1,6 @@ """DNS Authenticator for NS1 DNS.""" import logging +from typing import Optional from lexicon.providers import nsone import zope.interface @@ -8,6 +9,7 @@ from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -26,12 +28,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 60 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + super().add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='NS1 credentials file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -54,6 +56,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_nsone_client().del_txt_record(domain, validation_name, validation) def _get_nsone_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _NS1LexiconClient(self.credentials.conf('api-key'), self.ttl) @@ -63,7 +67,7 @@ class _NS1LexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, api_key, ttl): - super(_NS1LexiconClient, self).__init__() + super().__init__() config = dns_common_lexicon.build_lexicon_config('nsone', { 'ttl': ttl, diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index d60412f27..6d3218221 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/tests/dns_nsone_test.py b/certbot-dns-nsone/tests/dns_nsone_test.py index 83371252f..3754f9811 100644 --- a/certbot-dns-nsone/tests/dns_nsone_test.py +++ b/certbot-dns-nsone/tests/dns_nsone_test.py @@ -21,7 +21,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_nsone._internal.dns_nsone import Authenticator diff --git a/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py index 11ae6b8f0..54fd6d791 100644 --- a/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py +++ b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py @@ -1,5 +1,6 @@ """DNS Authenticator for OVH DNS.""" import logging +from typing import Optional from lexicon.providers import ovh import zope.interface @@ -8,6 +9,7 @@ from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -26,12 +28,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 60 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + super().add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='OVH credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -60,6 +62,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_ovh_client().del_txt_record(domain, validation_name, validation) def _get_ovh_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _OVHLexiconClient( self.credentials.conf('endpoint'), self.credentials.conf('application-key'), @@ -75,7 +79,7 @@ class _OVHLexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, endpoint, application_key, application_secret, consumer_key, ttl): - super(_OVHLexiconClient, self).__init__() + super().__init__() config = dns_common_lexicon.build_lexicon_config('ovh', { 'ttl': ttl, @@ -102,4 +106,4 @@ class _OVHLexiconClient(dns_common_lexicon.LexiconClient): if domain_name in str(e) and str(e).endswith('not found'): return - super(_OVHLexiconClient, self)._handle_general_error(e, domain_name) + super()._handle_general_error(e, domain_name) diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 4d6131ff7..e8e44db30 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/tests/dns_ovh_test.py b/certbot-dns-ovh/tests/dns_ovh_test.py index dd0f3058b..7f93967eb 100644 --- a/certbot-dns-ovh/tests/dns_ovh_test.py +++ b/certbot-dns-ovh/tests/dns_ovh_test.py @@ -23,7 +23,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_ovh._internal.dns_ovh import Authenticator diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py index adebdd963..28fd27eec 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -1,5 +1,6 @@ """DNS Authenticator using RFC 2136 Dynamic Updates.""" import logging +from typing import Optional import dns.flags import dns.message @@ -15,6 +16,7 @@ import zope.interface from certbot import errors from certbot import interfaces from certbot.plugins import dns_common +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -43,12 +45,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 120 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) + super().add_parser_arguments(add, default_propagation_seconds=60) add('credentials', help='RFC 2136 credentials INI file.') def more_info(self): # pylint: disable=missing-function-docstring @@ -80,6 +82,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_rfc2136_client().del_txt_record(validation_name, validation) def _get_rfc2136_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _RFC2136Client(self.credentials.conf('server'), int(self.credentials.conf('port') or self.PORT), self.credentials.conf('name'), diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 18d006732..6b4648d91 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/tests/dns_rfc2136_test.py b/certbot-dns-rfc2136/tests/dns_rfc2136_test.py index bb1799a45..de77c1bcc 100644 --- a/certbot-dns-rfc2136/tests/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/tests/dns_rfc2136_test.py @@ -28,7 +28,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic def setUp(self): from certbot_dns_rfc2136._internal.dns_rfc2136 import Authenticator - super(AuthenticatorTest, self).setUp() + super().setUp() path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write(VALID_CONFIG, path) diff --git a/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py index 4dda13f1d..9e470bdde 100644 --- a/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py +++ b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py @@ -37,7 +37,7 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 10 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.r53 = boto3.client("route53") self._resource_records: DefaultDict[str, List[Dict[str, str]]] = collections.defaultdict(list) diff --git a/certbot-dns-route53/certbot_dns_route53/authenticator.py b/certbot-dns-route53/certbot_dns_route53/authenticator.py index 2987934a1..060d2fa38 100644 --- a/certbot-dns-route53/certbot_dns_route53/authenticator.py +++ b/certbot-dns-route53/certbot_dns_route53/authenticator.py @@ -18,4 +18,4 @@ class Authenticator(dns_route53.Authenticator): def __init__(self, *args, **kwargs): warnings.warn("The 'authenticator' module was renamed 'dns_route53'", DeprecationWarning) - super(Authenticator, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index fdc931bbe..30c45dc84 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/tests/dns_route53_test.py b/certbot-dns-route53/tests/dns_route53_test.py index 1fd191c69..69b6b115d 100644 --- a/certbot-dns-route53/tests/dns_route53_test.py +++ b/certbot-dns-route53/tests/dns_route53_test.py @@ -21,7 +21,7 @@ class AuthenticatorTest(unittest.TestCase, dns_test_common.BaseAuthenticatorTest def setUp(self): from certbot_dns_route53._internal.dns_route53 import Authenticator - super(AuthenticatorTest, self).setUp() + super().setUp() self.config = mock.MagicMock() diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py index 67cfb2e97..668c5e1f8 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py @@ -1,12 +1,15 @@ """DNS Authenticator for Sakura Cloud DNS.""" import logging +from typing import Optional from lexicon.providers import sakuracloud import zope.interface +from certbot import errors from certbot import interfaces from certbot.plugins import dns_common from certbot.plugins import dns_common_lexicon +from certbot.plugins.dns_common import CredentialsConfiguration logger = logging.getLogger(__name__) @@ -26,12 +29,12 @@ class Authenticator(dns_common.DNSAuthenticator): ttl = 60 def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None + super().__init__(*args, **kwargs) + self.credentials: Optional[CredentialsConfiguration] = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments( + super().add_parser_arguments( add, default_propagation_seconds=90) add('credentials', help='Sakura Cloud credentials file.') @@ -60,6 +63,8 @@ class Authenticator(dns_common.DNSAuthenticator): domain, validation_name, validation) def _get_sakuracloud_client(self): + if not self.credentials: # pragma: no cover + raise errors.Error("Plugin has not been prepared.") return _SakuraCloudLexiconClient( self.credentials.conf('api-token'), self.credentials.conf('api-secret'), @@ -73,7 +78,7 @@ class _SakuraCloudLexiconClient(dns_common_lexicon.LexiconClient): """ def __init__(self, api_token, api_secret, ttl): - super(_SakuraCloudLexiconClient, self).__init__() + super().__init__() config = dns_common_lexicon.build_lexicon_config('sakuracloud', { 'ttl': ttl, @@ -87,4 +92,4 @@ class _SakuraCloudLexiconClient(dns_common_lexicon.LexiconClient): def _handle_http_error(self, e, domain_name): if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')): return None # Expected errors when zone name guess is wrong - return super(_SakuraCloudLexiconClient, self)._handle_http_error(e, domain_name) + return super()._handle_http_error(e, domain_name) diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 55f140d76..c328429c9 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py b/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py index af94336b3..1c64df372 100644 --- a/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py +++ b/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py @@ -21,7 +21,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() from certbot_dns_sakuracloud._internal.dns_sakuracloud import Authenticator diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index b1eeb3975..b2364c29f 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -102,7 +102,7 @@ class NginxConfigurator(common.Installer): """ version = kwargs.pop("version", None) openssl_version = kwargs.pop("openssl_version", None) - super(NginxConfigurator, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Files to save self.save_notes = "" @@ -119,7 +119,6 @@ class NginxConfigurator(common.Installer): self._chall_out = 0 # These will be set in the prepare function - self.parser: Optional[parser.NginxParser] = None self.version = version self.openssl_version = openssl_version self._enhance_func = {"redirect": self._enable_redirect, @@ -127,6 +126,7 @@ class NginxConfigurator(common.Installer): "staple-ocsp": self._enable_ocsp_stapling} self.reverter.recovery_routine() + self.parser: parser.NginxParser @property def mod_ssl_conf_src(self): @@ -1110,7 +1110,7 @@ class NginxConfigurator(common.Installer): :raises .errors.PluginError: If unable to recover the configuration """ - super(NginxConfigurator, self).recovery_routine() + super().recovery_routine() self.new_vhost = None self.parser.load() @@ -1133,7 +1133,7 @@ class NginxConfigurator(common.Installer): the function is unable to correctly revert the configuration """ - super(NginxConfigurator, self).rollback_checkpoints(rollback) + super().rollback_checkpoints(rollback) self.new_vhost = None self.parser.load() diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py index b1c7acd24..eeca6c855 100644 --- a/certbot-nginx/certbot_nginx/_internal/http_01.py +++ b/certbot-nginx/certbot_nginx/_internal/http_01.py @@ -37,7 +37,7 @@ class NginxHttp01(common.ChallengePerformer): """ def __init__(self, configurator): - super(NginxHttp01, self).__init__(configurator) + super().__init__(configurator) self.challenge_conf = os.path.join( configurator.config.config_dir, "le_http_01_cert_challenge.conf") diff --git a/certbot-nginx/certbot_nginx/_internal/obj.py b/certbot-nginx/certbot_nginx/_internal/obj.py index f77e535ce..44be0e598 100644 --- a/certbot-nginx/certbot_nginx/_internal/obj.py +++ b/certbot-nginx/certbot_nginx/_internal/obj.py @@ -35,7 +35,7 @@ class Addr(common.Addr): CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0] def __init__(self, host, port, ssl, default, ipv6, ipv6only): - super(Addr, self).__init__((host, port)) + super().__init__((host, port)) self.ssl = ssl self.default = default self.ipv6 = ipv6 @@ -120,7 +120,7 @@ class Addr(common.Addr): def __hash__(self): # pylint: disable=useless-super-delegation # Python 3 requires explicit overridden for __hash__ # See certbot-apache/certbot_apache/_internal/obj.py for more information - return super(Addr, self).__hash__() + return super().__hash__() def super_eq(self, other): """Check ip/port equality, with IPv6 support. @@ -132,7 +132,7 @@ class Addr(common.Addr): self.tup[1]), self.ipv6) == \ common.Addr((other.CANONICAL_UNSPECIFIED_ADDRESS, other.tup[1]), other.ipv6) - return super(Addr, self).__eq__(other) + return super().__eq__(other) def __eq__(self, other): if isinstance(other, self.__class__): diff --git a/certbot-nginx/certbot_nginx/_internal/parser_obj.py b/certbot-nginx/certbot_nginx/_internal/parser_obj.py index 838a2e382..d4d332c47 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser_obj.py +++ b/certbot-nginx/certbot_nginx/_internal/parser_obj.py @@ -122,7 +122,7 @@ class Statements(Parsable): precede any more statements. """ def __init__(self, parent=None): - super(Statements, self).__init__(parent) + super().__init__(parent) self._trailing_whitespace = None # ======== Begin overridden functions @@ -167,7 +167,7 @@ class Statements(Parsable): def dump(self, include_spaces=False): """ Dumps this object by first dumping each statement, then appending its trailing whitespace (if `include_spaces` is set) """ - data = super(Statements, self).dump(include_spaces) + data = super().dump(include_spaces) if include_spaces and self._trailing_whitespace is not None: return data + [self._trailing_whitespace] return data @@ -271,7 +271,7 @@ class Block(Parsable): contents = [["\n ", "content", " ", "1"], ["\n ", "content", " ", "2"], "\n"] """ def __init__(self, parent=None): - super(Block, self).__init__(parent) + super().__init__(parent) self.names: Sentence = None self.contents: Block = None diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 8b638f28e..11df5eddf 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -1,7 +1,7 @@ from setuptools import find_packages from setuptools import setup -version = '1.14.0.dev0' +version = '1.15.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py index 9ccc3fc9e..e6ceca344 100644 --- a/certbot-nginx/tests/configurator_test.py +++ b/certbot-nginx/tests/configurator_test.py @@ -26,7 +26,7 @@ class NginxConfiguratorTest(util.NginxTest): def setUp(self): - super(NginxConfiguratorTest, self).setUp() + super().setUp() self.config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) @@ -954,7 +954,7 @@ class InstallSslOptionsConfTest(util.NginxTest): """Test that the options-ssl-nginx.conf file is installed and updated properly.""" def setUp(self): - super(InstallSslOptionsConfTest, self).setUp() + super().setUp() self.config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) diff --git a/certbot-nginx/tests/display_ops_test.py b/certbot-nginx/tests/display_ops_test.py index 377255441..bcd455410 100644 --- a/certbot-nginx/tests/display_ops_test.py +++ b/certbot-nginx/tests/display_ops_test.py @@ -12,7 +12,7 @@ class SelectVhostMultiTest(util.NginxTest): """Tests for certbot_nginx._internal.display_ops.select_vhost_multiple.""" def setUp(self): - super(SelectVhostMultiTest, self).setUp() + super().setUp() nparser = parser.NginxParser(self.config_path) self.vhosts = nparser.get_vhosts() diff --git a/certbot-nginx/tests/http_01_test.py b/certbot-nginx/tests/http_01_test.py index d0e84fc83..f10e44859 100644 --- a/certbot-nginx/tests/http_01_test.py +++ b/certbot-nginx/tests/http_01_test.py @@ -51,7 +51,7 @@ class HttpPerformTest(util.NginxTest): ] def setUp(self): - super(HttpPerformTest, self).setUp() + super().setUp() config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) diff --git a/certbot-nginx/tests/test_util.py b/certbot-nginx/tests/test_util.py index ee53e8e1e..383a15753 100644 --- a/certbot-nginx/tests/test_util.py +++ b/certbot-nginx/tests/test_util.py @@ -22,7 +22,7 @@ from certbot_nginx._internal import nginxparser class NginxTest(test_util.ConfigTestCase): def setUp(self): - super(NginxTest, self).setUp() + super().setUp() self.configuration = self.config self.config = None diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 9b37e9a1a..7648d0ec3 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,7 +2,23 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 1.14.0 - master +## 1.15.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + +## 1.14.0 - 2021-04-06 ### Added diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 790443bcc..fbf8160fc 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,3 +1,3 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.14.0.dev0' +__version__ = '1.15.0.dev0' diff --git a/certbot/certbot/_internal/cli/cli_utils.py b/certbot/certbot/_internal/cli/cli_utils.py index 1f30261c1..8060b5e21 100644 --- a/certbot/certbot/_internal/cli/cli_utils.py +++ b/certbot/certbot/_internal/cli/cli_utils.py @@ -140,7 +140,7 @@ class CaseInsensitiveList(list): through the `helpful` wrapper. It is necessary due to special handling of command line arguments by `set_by_cli` in which the `type_func` is not applied.""" def __contains__(self, element): - return super(CaseInsensitiveList, self).__contains__(element.lower()) + return super().__contains__(element.lower()) def _user_agent_comment_type(value): diff --git a/certbot/certbot/_internal/cli/subparsers.py b/certbot/certbot/_internal/cli/subparsers.py index 822381d21..dabb26661 100644 --- a/certbot/certbot/_internal/cli/subparsers.py +++ b/certbot/certbot/_internal/cli/subparsers.py @@ -32,8 +32,7 @@ def _create_subparsers(helpful): " Currently --csr only works with the 'certonly' subcommand.") helpful.add("revoke", "--reason", dest="reason", - choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS, - key=constants.REVOCATION_REASONS.get)), + choices=CaseInsensitiveList(constants.REVOCATION_REASONS.keys()), action=_EncodeReasonAction, default=flag_default("reason"), help="Specify reason for revoking certificate. (default: unspecified)") helpful.add("revoke", diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py index b67d15ba6..09defc886 100644 --- a/certbot/certbot/_internal/client.py +++ b/certbot/certbot/_internal/client.py @@ -254,6 +254,7 @@ class Client: acme = acme_from_config_key(config, self.account.key, self.account.regr) self.acme = acme + self.auth_handler: Optional[auth_handler.AuthHandler] if auth is not None: self.auth_handler = auth_handler.AuthHandler( auth, self.acme, self.account, self.config.pref_challs) @@ -407,6 +408,9 @@ class Client: raise errors.Error("The currently selected ACME CA endpoint does" " not support issuing wildcard certificates.") + if not self.auth_handler: + raise errors.Error("No authorization handler has been set.") + # For a dry run, ensure we have an order with fresh authorizations if orderr and self.config.dry_run: deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr) diff --git a/certbot/certbot/_internal/display/completer.py b/certbot/certbot/_internal/display/completer.py index a6c984195..b43859b19 100644 --- a/certbot/certbot/_internal/display/completer.py +++ b/certbot/certbot/_internal/display/completer.py @@ -1,5 +1,8 @@ """Provides Tab completion when prompting users for a path.""" import glob +from typing import Callable +from typing import Iterator +from typing import Optional # readline module is not available on all systems try: @@ -26,7 +29,9 @@ class Completer: """ def __init__(self): - self._iter = self._original_completer = self._original_delims = None + self._iter: Iterator[str] + self._original_completer: Optional[Callable] + self._original_delims: str def complete(self, text, state): """Provides path completion for use with readline. diff --git a/certbot/certbot/_internal/lock.py b/certbot/certbot/_internal/lock.py index d00302598..80b79d993 100644 --- a/certbot/certbot/_internal/lock.py +++ b/certbot/certbot/_internal/lock.py @@ -205,10 +205,9 @@ class _WindowsLockMechanism(_BaseLockMechanism): # Under Windows, filesystem.open will raise directly an EACCES error # if the lock file is already locked. fd = filesystem.open(self._path, open_mode, 0o600) - # The need for this "type: ignore" was fixed in - # https://github.com/python/typeshed/pull/3607 and included in - # newer versions of mypy so it can be removed when mypy is - # upgraded. + # This "type: ignore" is currently needed because msvcrt methods + # are only defined on Windows. See + # https://github.com/python/typeshed/blob/16ae4c61201cd8b96b8b22cdfb2ab9e89ba5bcf2/stdlib/msvcrt.pyi. msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) # type: ignore except (IOError, OSError) as err: if fd: @@ -224,10 +223,11 @@ class _WindowsLockMechanism(_BaseLockMechanism): def release(self): """Release the lock.""" try: - # The need for this "type: ignore" was fixed in - # https://github.com/python/typeshed/pull/3607 and included in - # newer versions of mypy so it can be removed when mypy is - # upgraded. + if not self._fd: + raise errors.Error("The lock has not been acquired first.") + # This "type: ignore" is currently needed because msvcrt methods + # are only defined on Windows. See + # https://github.com/python/typeshed/blob/16ae4c61201cd8b96b8b22cdfb2ab9e89ba5bcf2/stdlib/msvcrt.pyi. msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1) # type: ignore os.close(self._fd) diff --git a/certbot/certbot/_internal/log.py b/certbot/certbot/_internal/log.py index beba10f57..ba28087cb 100644 --- a/certbot/certbot/_internal/log.py +++ b/certbot/certbot/_internal/log.py @@ -171,7 +171,7 @@ class ColoredStreamHandler(logging.StreamHandler): """ def __init__(self, stream=None): - super(ColoredStreamHandler, self).__init__(stream) + super().__init__(stream) self.colored = (sys.stderr.isatty() if stream is None else stream.isatty()) self.red_level = logging.WARNING @@ -185,7 +185,7 @@ class ColoredStreamHandler(logging.StreamHandler): :rtype: str """ - out = super(ColoredStreamHandler, self).format(record) + out = super().format(record) if self.colored and record.levelno >= self.red_level: return ''.join((util.ANSI_SGR_RED, out, util.ANSI_SGR_RESET)) return out @@ -200,14 +200,14 @@ class MemoryHandler(logging.handlers.MemoryHandler): """ def __init__(self, target=None, capacity=10000): # capacity doesn't matter because should_flush() is overridden - super(MemoryHandler, self).__init__(capacity, target=target) + super().__init__(capacity, target=target) def close(self): """Close the memory handler, but don't set the target to None.""" # This allows the logging module which may only have a weak # reference to the target handler to properly flush and close it. target = getattr(self, 'target') - super(MemoryHandler, self).close() + super().close() self.target = target def flush(self, force=False): # pylint: disable=arguments-differ @@ -221,7 +221,7 @@ class MemoryHandler(logging.handlers.MemoryHandler): # This method allows flush() calls in logging.shutdown to be a # noop so we can control when this handler is flushed. if force: - super(MemoryHandler, self).flush() + super().flush() def shouldFlush(self, record): """Should the buffer be automatically flushed? @@ -249,7 +249,7 @@ class TempHandler(logging.StreamHandler): self._workdir = tempfile.mkdtemp() self.path = os.path.join(self._workdir, 'log') stream = util.safe_open(self.path, mode='w', chmod=0o600) - super(TempHandler, self).__init__(stream) + super().__init__(stream) self._delete = True def emit(self, record): @@ -259,7 +259,7 @@ class TempHandler(logging.StreamHandler): """ self._delete = False - super(TempHandler, self).emit(record) + super().emit(record) def close(self): """Close the handler and the temporary log file. @@ -275,7 +275,7 @@ class TempHandler(logging.StreamHandler): if self._delete: shutil.rmtree(self._workdir) self._delete = False - super(TempHandler, self).close() + super().close() finally: self.release() diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 5f690b419..703ee0452 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -4,6 +4,9 @@ import functools import logging.handlers import sys +from contextlib import contextmanager +from typing import Generator +from typing import IO from typing import Iterable from typing import List from typing import Optional @@ -1347,26 +1350,36 @@ def make_or_verify_needed_dirs(config): util.make_or_verify_dir(hook_dir, strict=config.strict_permissions) -def set_displayer(config): - """Set the displayer +@contextmanager +def make_displayer(config: configuration.NamespaceConfig + ) -> Generator[Union[display_util.NoninteractiveDisplay, + display_util.FileDisplay], None, None]: + """Creates a display object appropriate to the flags in the supplied config. :param config: Configuration object - :type config: interfaces.IConfig - :returns: `None` - :rtype: None + :returns: Display object implementing :class:`certbot.interfaces.IDisplay` """ + displayer: Union[None, display_util.NoninteractiveDisplay, + display_util.FileDisplay] = None + devnull: Optional[IO] = None + if config.quiet: config.noninteractive_mode = True - displayer: Union[None, display_util.NoninteractiveDisplay, display_util.FileDisplay] =\ - display_util.NoninteractiveDisplay(open(os.devnull, "w")) + devnull = open(os.devnull, "w") + displayer = display_util.NoninteractiveDisplay(devnull) elif config.noninteractive_mode: displayer = display_util.NoninteractiveDisplay(sys.stdout) else: - displayer = display_util.FileDisplay(sys.stdout, - config.force_interactive) - zope.component.provideUtility(displayer) + displayer = display_util.FileDisplay( + sys.stdout, config.force_interactive) + + try: + yield displayer + finally: + if devnull: + devnull.close() def main(cli_args=None): @@ -1411,11 +1424,12 @@ def main(cli_args=None): if config.func != plugins_cmd: # pylint: disable=comparison-with-callable raise - set_displayer(config) - # Reporter report = reporter.Reporter(config) zope.component.provideUtility(report) util.atexit_register(report.print_messages) - return config.func(config, plugins) + with make_displayer(config) as displayer: + zope.component.provideUtility(displayer) + + return config.func(config, plugins) diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index 4097544e8..744062968 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -1,18 +1,20 @@ """Utilities for plugins discovery and selection.""" -from collections.abc import Mapping import itertools import logging import sys +from collections.abc import Mapping from typing import Dict +from typing import Optional +from typing import Union import pkg_resources import zope.interface import zope.interface.verify - from certbot import errors from certbot import interfaces from certbot._internal import constants from certbot.compat import os +from certbot.errors import Error logger = logging.getLogger(__name__) @@ -44,15 +46,15 @@ class PluginEntryPoint: # this object is mutable, don't allow it to be hashed! __hash__ = None # type: ignore - def __init__(self, entry_point, with_prefix=False): + def __init__(self, entry_point: pkg_resources.EntryPoint, with_prefix=False): self.name = self.entry_point_to_plugin_name(entry_point, with_prefix) - self.plugin_cls = entry_point.load() + self.plugin_cls: interfaces.IPluginFactory = entry_point.load() self.entry_point = entry_point - self.warning_message = None - self._initialized = None - self._prepared = None + self.warning_message: Optional[str] = None + self._initialized: Optional[interfaces.IPlugin] = None + self._prepared: Optional[Union[bool, Error]] = None self._hidden = False - self._long_description = None + self._long_description: Optional[str] = None def check_name(self, name): """Check if the name refers to this plugin.""" @@ -118,12 +120,15 @@ class PluginEntryPoint: """Memoized plugin initialization.""" if not self.initialized: self.entry_point.require() # fetch extras! - self._initialized = self.plugin_cls(config, self.name) + # TODO: remove type ignore once the interface becomes a proper + # abstract class (using abc) that mypy understands. + self._initialized = self.plugin_cls(config, self.name) # type: ignore return self._initialized def verify(self, ifaces): """Verify that the plugin conforms to the specified interfaces.""" - assert self.initialized + if not self.initialized: + raise ValueError("Plugin is not initialized.") for iface in ifaces: # zope.interface.providedBy(plugin) try: zope.interface.verify.verifyObject(iface, self.init()) @@ -144,10 +149,13 @@ class PluginEntryPoint: def prepare(self): """Memoized plugin preparation.""" - assert self.initialized + if self._initialized is None: + raise ValueError("Plugin is not initialized.") if self._prepared is None: try: - self._initialized.prepare() + # TODO: remove type ignore once the interface becomes a proper + # abstract class (using abc) that mypy understands. + self._initialized.prepare() # type: ignore except errors.MisconfigurationError as error: logger.debug("Misconfigured %r: %s", self, error, exc_info=True) self._prepared = error @@ -247,8 +255,10 @@ class PluginsRegistry(Mapping): plugin_ep = PluginEntryPoint(entry_point, with_prefix) if plugin_ep.name in plugins: other_ep = plugins[plugin_ep.name] + plugin1 = plugin_ep.entry_point.dist.key if plugin_ep.entry_point.dist else "unknown" + plugin2 = other_ep.entry_point.dist.key if other_ep.entry_point.dist else "unknown" 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)) + plugin_ep.name, plugin1, plugin2)) if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): plugins[plugin_ep.name] = plugin_ep else: # pragma: no cover diff --git a/certbot/certbot/_internal/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py index ed2e0559e..dec73a1ed 100644 --- a/certbot/certbot/_internal/plugins/manual.py +++ b/certbot/certbot/_internal/plugins/manual.py @@ -43,13 +43,30 @@ class Authenticator(common.Plugin): '$CERTBOT_REMAINING_CHALLENGES will be equal to the number of challenges that ' 'remain after the current one, and $CERTBOT_ALL_DOMAINS contains a comma-separated ' 'list of all domains that are challenged for the current certificate.') + # Include the full stop at the end of the FQDN in the instructions below for the null + # label of the DNS root, as stated in section 3.1 of RFC 1035. While not necessary + # for most day to day usage of hostnames, when adding FQDNs to a DNS zone editor, this + # full stop is often mandatory. Without a full stop, the entered name is often seen as + # relative to the DNS zone origin, which could lead to entries for, e.g.: + # _acme-challenge.example.com.example.com. For users unaware of this subtle detail, + # including the trailing full stop in the DNS instructions below might avert this issue. _DNS_INSTRUCTIONS = """\ -Please deploy a DNS TXT record under the name -{domain} with the following value: +Please deploy a DNS TXT record under the name: + +{domain}. + +with the following value: {validation} - -Before continuing, verify the record is deployed.""" +""" + _DNS_VERIFY_INSTRUCTIONS = """ +Before continuing, verify the TXT record has been deployed. Depending on the DNS +provider, this may take some time, from a few seconds to multiple minutes. You can +check if it has finished deploying with aid of online tools, such as the Google +Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/{domain}. +Look for one or more bolded line(s) below the line ';ANSWER'. It should show the +value(s) you've just added. +""" _HTTP_INSTRUCTIONS = """\ Create a file containing just this data: @@ -71,7 +88,7 @@ permitted by DNS standards.) """ def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.reverter = reverter.Reverter(self.config) self.reverter.recovery_routine() self.env: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] = {} @@ -114,11 +131,15 @@ permitted by DNS standards.) def perform(self, achalls): # pylint: disable=missing-function-docstring responses = [] - for achall in achalls: + last_dns_achall = 0 + for i, achall in enumerate(achalls): + if isinstance(achall.chall, challenges.DNS01): + last_dns_achall = i + for i, achall in enumerate(achalls): if self.conf('auth-hook'): self._perform_achall_with_script(achall, achalls) else: - self._perform_achall_manually(achall) + self._perform_achall_manually(achall, i == last_dns_achall) responses.append(achall.response(achall.account_key)) return responses @@ -136,7 +157,7 @@ permitted by DNS standards.) env['CERTBOT_AUTH_OUTPUT'] = out.strip() self.env[achall] = env - def _perform_achall_manually(self, achall): + def _perform_achall_manually(self, achall, last_dns_achall=False): validation = achall.validation(achall.account_key) if isinstance(achall.chall, challenges.HTTP01): msg = self._HTTP_INSTRUCTIONS.format( @@ -152,7 +173,15 @@ permitted by DNS standards.) if self.subsequent_dns_challenge: # 2nd or later dns-01 challenge msg += self._SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS + elif self.subsequent_any_challenge: + # 1st dns-01 challenge, but 2nd or later *any* challenge, so + # instruct user not to remove any previous http-01 challenge + msg += self._SUBSEQUENT_CHALLENGE_INSTRUCTIONS self.subsequent_dns_challenge = True + if last_dns_achall: + # last dns-01 challenge + msg += self._DNS_VERIFY_INSTRUCTIONS.format( + domain=achall.validation_domain_name(achall.domain)) elif self.subsequent_any_challenge: # 2nd or later challenge of another type msg += self._SUBSEQUENT_CHALLENGE_INSTRUCTIONS diff --git a/certbot/certbot/_internal/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py index 833651346..5fb29671f 100644 --- a/certbot/certbot/_internal/plugins/standalone.py +++ b/certbot/certbot/_internal/plugins/standalone.py @@ -1,9 +1,8 @@ """Standalone Authenticator.""" import collections +import errno import logging import socket -# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi -from socket import errno as socket_errors # type: ignore from typing import DefaultDict from typing import Dict from typing import Set @@ -120,7 +119,7 @@ class Authenticator(common.Plugin): description = "Spin up a temporary webserver" def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.served: ServedType = collections.defaultdict(set) @@ -187,13 +186,13 @@ class Authenticator(common.Plugin): def _handle_perform_error(error): - if error.socket_error.errno == socket_errors.EACCES: + if error.socket_error.errno == errno.EACCES: raise errors.PluginError( "Could not bind TCP port {0} because you don't have " "the appropriate permissions (for example, you " "aren't running this program as " "root).".format(error.port)) - if error.socket_error.errno == socket_errors.EADDRINUSE: + if error.socket_error.errno == errno.EADDRINUSE: display = zope.component.getUtility(interfaces.IDisplay) msg = ( "Could not bind TCP port {0} because it is already in " diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py index 25752c387..d33d2ec57 100644 --- a/certbot/certbot/_internal/plugins/webroot.py +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -66,7 +66,7 @@ to serve all files under specified web root ({0}).""" return [challenges.HTTP01] def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.full_roots: Dict[str, str] = {} self.performed: DefaultDict[str, Set[AnnotatedChallenge]] = collections.defaultdict(set) # stack of dirs successfully created by this authenticator @@ -250,7 +250,7 @@ class _WebrootPathAction(argparse.Action): """Action class for parsing webroot_path.""" def __init__(self, *args, **kwargs): - super(_WebrootPathAction, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._domain_before_webroot = False def __call__(self, parser, namespace, webroot_path, option_string=None): diff --git a/certbot/certbot/_internal/snap_config.py b/certbot/certbot/_internal/snap_config.py index 1ba53cad4..f7832cd55 100644 --- a/certbot/certbot/_internal/snap_config.py +++ b/certbot/certbot/_internal/snap_config.py @@ -80,7 +80,7 @@ def prepare_env(cli_args: List[str]) -> List[str]: class _SnapdConnection(HTTPConnection): def __init__(self): - super(_SnapdConnection, self).__init__("localhost") + super().__init__("localhost") self.sock = None def connect(self): @@ -90,7 +90,7 @@ class _SnapdConnection(HTTPConnection): class _SnapdConnectionPool(HTTPConnectionPool): def __init__(self): - super(_SnapdConnectionPool, self).__init__("localhost") + super().__init__("localhost") def _new_conn(self): return _SnapdConnection() diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index 82f1305b8..678ed7e09 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -302,8 +302,7 @@ def verify_signed_payload(public_key, signature, payload, signature_hash_algorit with warnings.catch_warnings(): warnings.simplefilter("ignore") if isinstance(public_key, RSAPublicKey): - # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi - verifier = public_key.verifier( # type: ignore + verifier = public_key.verifier( signature, PKCS1v15(), signature_hash_algorithm ) verifier.update(payload) diff --git a/certbot/certbot/display/util.py b/certbot/certbot/display/util.py index 9443ae266..dc642586c 100644 --- a/certbot/certbot/display/util.py +++ b/certbot/certbot/display/util.py @@ -115,7 +115,7 @@ class FileDisplay: # see https://github.com/certbot/certbot/issues/3915 def __init__(self, outfile, force_interactive): - super(FileDisplay, self).__init__() + super().__init__() self.outfile = outfile self.force_interactive = force_interactive self.skipped_interaction = False @@ -481,7 +481,7 @@ class NoninteractiveDisplay: """An iDisplay implementation that never asks for interactive user input""" def __init__(self, outfile, *unused_args, **unused_kwargs): - super(NoninteractiveDisplay, self).__init__() + super().__init__() self.outfile = outfile def _interaction_fail(self, message, cli_flag, extra=""): diff --git a/certbot/certbot/errors.py b/certbot/certbot/errors.py index 48aebc267..cf0d3d283 100644 --- a/certbot/certbot/errors.py +++ b/certbot/certbot/errors.py @@ -53,7 +53,7 @@ class FailedChallenges(AuthorizationError): def __init__(self, failed_achalls): assert failed_achalls self.failed_achalls = failed_achalls - super(FailedChallenges, self).__init__() + super().__init__() def __str__(self): return "Failed authorization procedure. {0}".format( @@ -95,7 +95,7 @@ class StandaloneBindError(Error): """Standalone plugin bind error.""" def __init__(self, socket_error, port): - super(StandaloneBindError, self).__init__( + super().__init__( "Problem binding to port {0}: {1}".format(port, socket_error)) self.socket_error = socket_error self.port = port diff --git a/certbot/certbot/interfaces.py b/certbot/certbot/interfaces.py index 3830e2849..de9175def 100644 --- a/certbot/certbot/interfaces.py +++ b/certbot/certbot/interfaces.py @@ -1,5 +1,6 @@ """Certbot client interfaces.""" import abc +from typing import Optional import zope.interface @@ -327,7 +328,7 @@ class IInstaller(IPlugin): """ - def save(title=None, temporary=False): + def save(title: Optional[str] = None, temporary: bool = False): """Saves all changes to the configuration files. Both title and temporary are needed because a save may be diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py index 06acefc69..4c33acbab 100644 --- a/certbot/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -105,7 +105,7 @@ class Installer(Plugin): """ def __init__(self, *args, **kwargs): - super(Installer, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.storage = PluginStorage(self.config, self.name) self.reverter = reverter.Reverter(self.config) diff --git a/certbot/certbot/plugins/dns_common.py b/certbot/certbot/plugins/dns_common.py index 3b51700f7..9a2949468 100644 --- a/certbot/certbot/plugins/dns_common.py +++ b/certbot/certbot/plugins/dns_common.py @@ -25,7 +25,7 @@ class DNSAuthenticator(common.Plugin): """Base class for DNS Authenticators""" def __init__(self, config, name): - super(DNSAuthenticator, self).__init__(config, name) + super().__init__(config, name) self._attempt_cleanup = False @@ -142,7 +142,8 @@ class DNSAuthenticator(common.Plugin): setattr(self.config, self.dest(key), os.path.abspath(os.path.expanduser(new_value))) - def _configure_credentials(self, key, label, required_variables=None, validator=None): + def _configure_credentials(self, key, label, required_variables=None, + validator=None) -> 'CredentialsConfiguration': """ As `_configure_file`, but for a credential configuration file. diff --git a/certbot/certbot/plugins/dns_common_lexicon.py b/certbot/certbot/plugins/dns_common_lexicon.py index 10efa5f85..a4d46587e 100644 --- a/certbot/certbot/plugins/dns_common_lexicon.py +++ b/certbot/certbot/plugins/dns_common_lexicon.py @@ -17,8 +17,10 @@ from certbot.plugins import dns_common # if Lexicon is not available, obviously. try: from lexicon.config import ConfigResolver + from lexicon.providers.base import Provider except ImportError: ConfigResolver = None # type: ignore + Provider = None # type: ignore logger = logging.getLogger(__name__) @@ -29,7 +31,7 @@ class LexiconClient: """ def __init__(self): - self.provider = None + self.provider: Provider def add_txt_record(self, domain, record_name, record_content): """ diff --git a/certbot/certbot/plugins/dns_test_common.py b/certbot/certbot/plugins/dns_test_common.py index f6d7bdca8..7a8df9329 100644 --- a/certbot/certbot/plugins/dns_test_common.py +++ b/certbot/certbot/plugins/dns_test_common.py @@ -12,7 +12,7 @@ from certbot.tests import acme_util from certbot.tests import util as test_util if typing.TYPE_CHECKING: - from typing import Protocol + from typing_extensions import Protocol else: Protocol = object # type: ignore diff --git a/certbot/certbot/plugins/dns_test_common_lexicon.py b/certbot/certbot/plugins/dns_test_common_lexicon.py index f9c09e821..af56cb422 100644 --- a/certbot/certbot/plugins/dns_test_common_lexicon.py +++ b/certbot/certbot/plugins/dns_test_common_lexicon.py @@ -18,7 +18,7 @@ try: except ImportError: # pragma: no cover from unittest import mock # type: ignore if typing.TYPE_CHECKING: - from typing import Protocol + from typing_extensions import Protocol else: Protocol = object # type: ignore diff --git a/certbot/certbot/plugins/storage.py b/certbot/certbot/plugins/storage.py index 0d32d6850..602c62d1e 100644 --- a/certbot/certbot/plugins/storage.py +++ b/certbot/certbot/plugins/storage.py @@ -26,8 +26,8 @@ class PluginStorage: self._config = config self._classkey = classkey self._initialized = False - self._data = None - self._storagepath = None + self._data: Dict + self._storagepath: str def _initialize_storage(self): """Initializes PluginStorage data and reads current state from the disk diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py index 78236fa06..b892ad0a1 100644 --- a/certbot/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -27,6 +27,9 @@ from certbot.compat import os from certbot.display import util as display_util try: + # When we remove this deprecated import, we should also remove the + # "external-mock" test environment and the mock dependency listed in + # tools/pinning/pyproject.toml. import mock warnings.warn( "The external mock module is being used for backwards compatibility " @@ -331,7 +334,7 @@ class TempDirTestCase(unittest.TestCase): class ConfigTestCase(TempDirTestCase): """Test class which sets up a NamespaceConfig object.""" def setUp(self): - super(ConfigTestCase, self).setUp() + super().setUp() self.config = configuration.NamespaceConfig( mock.MagicMock(**constants.CLI_DEFAULTS) ) diff --git a/certbot/docs/ciphers.rst b/certbot/docs/ciphers.rst index 43f648898..b4ef5902a 100644 --- a/certbot/docs/ciphers.rst +++ b/certbot/docs/ciphers.rst @@ -1,3 +1,11 @@ +.. + Sphinx complains that this file isn't included in any toctree, however, we + currently link to it in the section about installing Certbot through Docker. + Setting :orphan: below suppresses this warning. See + https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html#special-metadata-fields. + +:orphan: + ============ Ciphersuites ============ diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index 27511777b..c5490c100 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -118,7 +118,7 @@ optional arguments: case, and to know when to deprecate support for past Python versions and flags. If you wish to hide this information from the Let's Encrypt server, set this to - "". (default: CertbotACMEClient/1.13.0 + "". (default: CertbotACMEClient/1.14.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/certbot/docs/conf.py b/certbot/docs/conf.py index 254bd3edd..5496c42a2 100644 --- a/certbot/docs/conf.py +++ b/certbot/docs/conf.py @@ -98,7 +98,6 @@ language = None exclude_patterns = [ '_build', 'challenges.rst', - 'ciphers.rst' ] # The reST default role (used for this markup: `text`) to use for all diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst index e38725ca3..cc061b622 100644 --- a/certbot/docs/using.rst +++ b/certbot/docs/using.rst @@ -284,6 +284,7 @@ dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server dns-clouddns_ Y N DNS Authentication using CloudDNS API dns-lightsail_ Y N DNS Authentication using Amazon Lightsail DNS API dns-inwx_ Y Y DNS Authentication for INWX through the XML API +dns-azure_ Y N DNS Authentication using Azure DNS ================== ==== ==== =============================================================== .. _haproxy: https://github.com/greenhost/certbot-haproxy @@ -298,6 +299,7 @@ dns-inwx_ Y Y DNS Authentication for INWX through the XML API .. _dns-clouddns: https://github.com/vshosting/certbot-dns-clouddns .. _dns-lightsail: https://github.com/noi/certbot-dns-lightsail .. _dns-inwx: https://github.com/oGGy990/certbot-dns-inwx/ +.. _dns-azure: https://github.com/binkhq/certbot-dns-azure If you're interested, you can also :ref:`write your own plugin `. diff --git a/certbot/setup.py b/certbot/setup.py index 8843a35a2..6913d8384 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -77,6 +77,9 @@ dev_extras = [ 'pytest', 'pytest-cov', 'pytest-xdist', + # typing-extensions is required to import typing.Protocol and make the mypy checks + # pass (along with pylint about non-existent objects) on Python 3.6 & 3.7 + 'typing-extensions', 'tox', 'twine', 'wheel', diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index d0448a1db..7c8f52273 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -106,7 +106,7 @@ class AccountFileStorageTest(test_util.ConfigTestCase): """Tests for certbot._internal.account.AccountFileStorage.""" def setUp(self): - super(AccountFileStorageTest, self).setUp() + super().setUp() from certbot._internal.account import AccountFileStorage self.storage = AccountFileStorage(self.config) diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index ba6cfddc3..918459256 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -26,7 +26,7 @@ class BaseCertManagerTest(test_util.ConfigTestCase): """Base class for setting up Cert Manager tests. """ def setUp(self): - super(BaseCertManagerTest, self).setUp() + super().setUp() self.config.quiet = False filesystem.makedirs(self.config.renewal_configs_dir) @@ -396,7 +396,7 @@ class RenameLineageTest(BaseCertManagerTest): """Tests for certbot._internal.cert_manager.rename_lineage""" def setUp(self): - super(RenameLineageTest, self).setUp() + super().setUp() self.config.certname = "example.org" self.config.new_certname = "after" @@ -483,7 +483,7 @@ class DuplicativeCertsTest(storage_test.BaseRenewableCertTest): """Test to avoid duplicate lineages.""" def setUp(self): - super(DuplicativeCertsTest, self).setUp() + super().setUp() self.config_file.write() self._write_out_ex_kinds() @@ -521,7 +521,7 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): """Tests for certbot._internal.cert_manager.cert_path_to_lineage""" def setUp(self): - super(CertPathToLineageTest, self).setUp() + super().setUp() self.config_file.write() self._write_out_ex_kinds() self.fullchain = os.path.join(self.config.config_dir, 'live', 'example.org', @@ -581,7 +581,7 @@ class MatchAndCheckOverlaps(storage_test.BaseRenewableCertTest): archive dirs.""" # A test with real overlapping archive dirs can be found in tests/boulder_integration.sh def setUp(self): - super(MatchAndCheckOverlaps, self).setUp() + super().setUp() self.config_file.write() self._write_out_ex_kinds() self.fullchain = os.path.join(self.config.config_dir, 'live', 'example.org', diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 34fbe8bdf..d8152b96d 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -58,7 +58,7 @@ class RegisterTest(test_util.ConfigTestCase): """Tests for certbot._internal.client.register.""" def setUp(self): - super(RegisterTest, self).setUp() + super().setUp() self.config.rsa_key_size = 1024 self.config.register_unsafely_without_email = False self.config.email = "alias@example.com" @@ -220,7 +220,7 @@ class ClientTestCommon(test_util.ConfigTestCase): """Common base class for certbot._internal.client.Client tests.""" def setUp(self): - super(ClientTestCommon, self).setUp() + super().setUp() self.config.no_verify_ssl = False self.config.allow_subset_of_names = False @@ -239,7 +239,7 @@ class ClientTest(ClientTestCommon): """Tests for certbot._internal.client.Client.""" def setUp(self): - super(ClientTest, self).setUp() + super().setUp() self.config.allow_subset_of_names = False self.config.dry_run = False @@ -585,7 +585,7 @@ class EnhanceConfigTest(ClientTestCommon): """Tests for certbot._internal.client.Client.enhance_config.""" def setUp(self): - super(EnhanceConfigTest, self).setUp() + super().setUp() self.config.hsts = False self.config.redirect = False diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index ea48c9d1c..47da2b415 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -34,7 +34,7 @@ ADMINS_SID = 'S-1-5-32-544' class WindowsChmodTests(TempDirTestCase): """Unit tests for Windows chmod function in filesystem module""" def setUp(self): - super(WindowsChmodTests, self).setUp() + super().setUp() self.probe_path = _create_probe(self.tempdir) def test_symlink_resolution(self): @@ -203,7 +203,7 @@ class UmaskTest(TempDirTestCase): class ComputePrivateKeyModeTest(TempDirTestCase): def setUp(self): - super(ComputePrivateKeyModeTest, self).setUp() + super().setUp() self.probe_path = _create_probe(self.tempdir) def test_compute_private_key_mode(self): @@ -351,7 +351,7 @@ class MakedirsTests(test_util.TempDirTestCase): class CopyOwnershipAndModeTest(test_util.TempDirTestCase): """Tests about copy_ownership_and_apply_mode, copy_ownership_and_mode and has_same_ownership""" def setUp(self): - super(CopyOwnershipAndModeTest, self).setUp() + super().setUp() self.probe_path = _create_probe(self.tempdir) @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') @@ -424,7 +424,7 @@ class CopyOwnershipAndModeTest(test_util.TempDirTestCase): class CheckPermissionsTest(test_util.TempDirTestCase): """Tests relative to functions that check modes.""" def setUp(self): - super(CheckPermissionsTest, self).setUp() + super().setUp() self.probe_path = _create_probe(self.tempdir) def test_check_mode(self): @@ -506,7 +506,7 @@ class OsReplaceTest(test_util.TempDirTestCase): class RealpathTest(test_util.TempDirTestCase): """Tests for realpath method""" def setUp(self): - super(RealpathTest, self).setUp() + super().setUp() self.probe_path = _create_probe(self.tempdir) def test_symlink_resolution(self): diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index 1f8a0e803..e23c50afb 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -17,7 +17,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): """Tests for certbot._internal.configuration.NamespaceConfig.""" def setUp(self): - super(NamespaceConfigTest, self).setUp() + super().setUp() self.config.foo = 'bar' # pylint: disable=blacklisted-name self.config.server = 'https://acme-server.org:443/new' self.config.https_port = 1234 diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index 9829c5f91..7c0d1bd2c 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -36,7 +36,7 @@ CERT_ALT_ISSUER = test_util.load_vector('cert_intermediate_2.pem') class InitSaveKeyTest(test_util.TempDirTestCase): """Tests for certbot.crypto_util.init_save_key.""" def setUp(self): - super(InitSaveKeyTest, self).setUp() + super().setUp() self.workdir = os.path.join(self.tempdir, 'workdir') filesystem.mkdir(self.workdir, mode=0o700) @@ -46,7 +46,7 @@ class InitSaveKeyTest(test_util.TempDirTestCase): mock.Mock(strict_permissions=True), interfaces.IConfig) def tearDown(self): - super(InitSaveKeyTest, self).tearDown() + super().tearDown() logging.disable(logging.NOTSET) @@ -73,7 +73,7 @@ class InitSaveCSRTest(test_util.TempDirTestCase): """Tests for certbot.crypto_util.init_save_csr.""" def setUp(self): - super(InitSaveCSRTest, self).setUp() + super().setUp() zope.component.provideUtility( mock.Mock(strict_permissions=True), interfaces.IConfig) diff --git a/certbot/tests/display/completer_test.py b/certbot/tests/display/completer_test.py index 86f939491..087617310 100644 --- a/certbot/tests/display/completer_test.py +++ b/certbot/tests/display/completer_test.py @@ -25,7 +25,7 @@ class CompleterTest(test_util.TempDirTestCase): """Test certbot._internal.display.completer.Completer.""" def setUp(self): - super(CompleterTest, self).setUp() + super().setUp() # directories must end with os.sep for completer to # search inside the directory for possible completions diff --git a/certbot/tests/display/ops_test.py b/certbot/tests/display/ops_test.py index ce60a5f0b..1e50ba03b 100644 --- a/certbot/tests/display/ops_test.py +++ b/certbot/tests/display/ops_test.py @@ -90,7 +90,7 @@ class GetEmailTest(unittest.TestCase): class ChooseAccountTest(test_util.TempDirTestCase): """Tests for certbot.display.ops.choose_account.""" def setUp(self): - super(ChooseAccountTest, self).setUp() + super().setUp() zope.component.provideUtility(display_util.FileDisplay(sys.stdout, False)) diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py index 30b33bbc9..5f1fac8c0 100644 --- a/certbot/tests/display/util_test.py +++ b/certbot/tests/display/util_test.py @@ -65,7 +65,7 @@ class FileOutputDisplayTest(unittest.TestCase): """ def setUp(self): - super(FileOutputDisplayTest, self).setUp() + super().setUp() self.mock_stdout = mock.MagicMock() self.displayer = display_util.FileDisplay(self.mock_stdout, False) diff --git a/certbot/tests/eff_test.py b/certbot/tests/eff_test.py index dc6b6b944..5da1e6fb6 100644 --- a/certbot/tests/eff_test.py +++ b/certbot/tests/eff_test.py @@ -22,7 +22,7 @@ _KEY = josepy.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) class SubscriptionTest(test_util.ConfigTestCase): """Abstract class for subscription tests.""" def setUp(self): - super(SubscriptionTest, self).setUp() + super().setUp() self.account = account.Account( regr=messages.RegistrationResource( uri=None, body=messages.Registration(), diff --git a/certbot/tests/error_handler_test.py b/certbot/tests/error_handler_test.py index 521b6976b..e04fe0742 100644 --- a/certbot/tests/error_handler_test.py +++ b/certbot/tests/error_handler_test.py @@ -136,7 +136,7 @@ class ExitHandlerTest(ErrorHandlerTest): def setUp(self): from certbot._internal import error_handler - super(ExitHandlerTest, self).setUp() + super().setUp() self.handler = error_handler.ExitHandler(self.init_func, *self.init_args, **self.init_kwargs) diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index 4133405c9..655f59e60 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -93,7 +93,7 @@ class PreHookTest(HookTest): return pre_hook(*args, **kwargs) def setUp(self): - super(PreHookTest, self).setUp() + super().setUp() self.config.pre_hook = "foo" filesystem.makedirs(self.config.renewal_pre_hooks_dir) @@ -106,7 +106,7 @@ class PreHookTest(HookTest): def tearDown(self): # Reset this value so it's unmodified for future tests self._reset_pre_hook_already() - super(PreHookTest, self).tearDown() + super().tearDown() def _reset_pre_hook_already(self): from certbot._internal.hooks import executed_pre_hooks @@ -171,7 +171,7 @@ class PostHookTest(HookTest): return post_hook(*args, **kwargs) def setUp(self): - super(PostHookTest, self).setUp() + super().setUp() self.config.post_hook = "bar" filesystem.makedirs(self.config.renewal_post_hooks_dir) @@ -184,7 +184,7 @@ class PostHookTest(HookTest): def tearDown(self): # Reset this value so it's unmodified for future tests self._reset_post_hook_eventually() - super(PostHookTest, self).tearDown() + super().tearDown() def _reset_post_hook_eventually(self): from certbot._internal.hooks import post_hooks @@ -266,7 +266,7 @@ class RunSavedPostHooksTest(HookTest): return self._call_with_mock_execute(*args, **kwargs) def setUp(self): - super(RunSavedPostHooksTest, self).setUp() + super().setUp() self.eventually: List[str] = [] def test_empty(self): @@ -319,7 +319,7 @@ class RenewalHookTest(HookTest): return mock_execute def setUp(self): - super(RenewalHookTest, self).setUp() + super().setUp() self.vars_to_clear = set( var for var in ("RENEWED_DOMAINS", "RENEWED_LINEAGE",) if var not in os.environ) @@ -327,7 +327,7 @@ class RenewalHookTest(HookTest): def tearDown(self): for var in self.vars_to_clear: os.environ.pop(var, None) - super(RenewalHookTest, self).tearDown() + super().tearDown() class DeployHookTest(RenewalHookTest): @@ -373,7 +373,7 @@ class RenewHookTest(RenewalHookTest): return renew_hook(*args, **kwargs) def setUp(self): - super(RenewHookTest, self).setUp() + super().setUp() self.config.renew_hook = "foo" filesystem.makedirs(self.config.renewal_deploy_hooks_dir) diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index 2f887d33e..ac6b539a4 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -44,7 +44,7 @@ class LockFileTest(test_util.TempDirTestCase): return LockFile(*args, **kwargs) def setUp(self): - super(LockFileTest, self).setUp() + super().setUp() self.lock_path = os.path.join(self.tempdir, 'test.lock') def test_acquire_without_deletion(self): diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index 7b1aacd5c..37660f51e 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -28,7 +28,8 @@ class PreArgParseSetupTest(unittest.TestCase): @classmethod def _call(cls, *args, **kwargs): # pylint: disable=unused-argument from certbot._internal.log import pre_arg_parse_setup - return pre_arg_parse_setup() + with mock.patch('builtins.open', mock.mock_open()): + return pre_arg_parse_setup() @mock.patch('certbot._internal.log.sys') @mock.patch('certbot._internal.log.pre_arg_parse_except_hook') @@ -69,7 +70,7 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): return post_arg_parse_setup(*args, **kwargs) def setUp(self): - super(PostArgParseSetupTest, self).setUp() + super().setUp() self.config.debug = False self.config.max_log_backups = 1000 self.config.quiet = False @@ -90,7 +91,7 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): self.stream_handler.close() self.temp_handler.close() self.devnull.close() - super(PostArgParseSetupTest, self).tearDown() + super().tearDown() def test_common(self): with mock.patch('certbot._internal.log.logging.getLogger') as mock_get_logger: @@ -135,7 +136,7 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase): return setup_log_file_handler(*args, **kwargs) def setUp(self): - super(SetupLogFileHandlerTest, self).setUp() + super().setUp() self.config.max_log_backups = 42 @mock.patch('certbot._internal.main.logging.handlers.RotatingFileHandler') diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index e093edd20..7908cf804 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -101,7 +101,7 @@ class RunTest(test_util.ConfigTestCase): """Tests for certbot._internal.main.run.""" def setUp(self): - super(RunTest, self).setUp() + super().setUp() self.domain = 'example.org' patches = [ mock.patch('certbot._internal.main._get_and_save_cert'), @@ -283,7 +283,7 @@ class RevokeTest(test_util.TempDirTestCase): """Tests for certbot._internal.main.revoke.""" def setUp(self): - super(RevokeTest, self).setUp() + super().setUp() shutil.copy(CERT_PATH, self.tempdir) self.tmp_cert_path = os.path.abspath(os.path.join(self.tempdir, 'cert_512.pem')) @@ -512,7 +512,7 @@ class DetermineAccountTest(test_util.ConfigTestCase): """Tests for certbot._internal.main._determine_account.""" def setUp(self): - super(DetermineAccountTest, self).setUp() + super().setUp() self.config.account = None self.config.email = None self.config.register_unsafely_without_email = False @@ -584,7 +584,7 @@ class MainTest(test_util.ConfigTestCase): """Tests for different commands.""" def setUp(self): - super(MainTest, self).setUp() + super().setUp() filesystem.mkdir(self.config.logs_dir) self.standard_args = ['--config-dir', self.config.config_dir, @@ -597,7 +597,7 @@ class MainTest(test_util.ConfigTestCase): # Reset globals in cli reload_module(cli) - super(MainTest, self).tearDown() + super().tearDown() def _call(self, args, stdout=None, mockisfile=False): """Run the cli with output streams, actual client and optionally @@ -1576,7 +1576,7 @@ class EnhanceTest(test_util.ConfigTestCase): """Tests for certbot._internal.main.enhance.""" def setUp(self): - super(EnhanceTest, self).setUp() + super().setUp() self.get_utility_patch = test_util.patch_get_utility() self.mock_get_utility = self.get_utility_patch.start() self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement) @@ -1720,7 +1720,7 @@ class InstallTest(test_util.ConfigTestCase): """Tests for certbot._internal.main.install.""" def setUp(self): - super(InstallTest, self).setUp() + super().setUp() self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement) @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @@ -1765,7 +1765,7 @@ class UpdateAccountTest(test_util.ConfigTestCase): for patch in patches.values(): self.addCleanup(patch.stop) - return super(UpdateAccountTest, self).setUp() + return super().setUp() def _call(self, args): with mock.patch('certbot._internal.main.sys.stdout'), \ diff --git a/certbot/tests/plugins/common_test.py b/certbot/tests/plugins/common_test.py index bcd190e9b..7de3134fa 100644 --- a/certbot/tests/plugins/common_test.py +++ b/certbot/tests/plugins/common_test.py @@ -88,7 +88,7 @@ class InstallerTest(test_util.ConfigTestCase): """Tests for certbot.plugins.common.Installer.""" def setUp(self): - super(InstallerTest, self).setUp() + super().setUp() filesystem.mkdir(self.config.config_dir) from certbot.plugins.common import Installer @@ -282,7 +282,7 @@ class InstallVersionControlledFileTest(test_util.TempDirTestCase): """Tests for certbot.plugins.common.install_version_controlled_file.""" def setUp(self): - super(InstallVersionControlledFileTest, self).setUp() + super().setUp() self.hashes = ["someotherhash"] self.dest_path = os.path.join(self.tempdir, "options-ssl-dest.conf") self.hash_path = os.path.join(self.tempdir, ".options-ssl-conf.txt") diff --git a/certbot/tests/plugins/dns_common_lexicon_test.py b/certbot/tests/plugins/dns_common_lexicon_test.py index a67430f3e..40afd107b 100644 --- a/certbot/tests/plugins/dns_common_lexicon_test.py +++ b/certbot/tests/plugins/dns_common_lexicon_test.py @@ -17,7 +17,7 @@ class LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconCl pass def setUp(self): - super(LexiconClientTest, self).setUp() + super().setUp() self.client = LexiconClientTest._FakeLexiconClient() self.provider_mock = mock.MagicMock() diff --git a/certbot/tests/plugins/dns_common_test.py b/certbot/tests/plugins/dns_common_test.py index f887a79dc..3b3f9c0a2 100644 --- a/certbot/tests/plugins/dns_common_test.py +++ b/certbot/tests/plugins/dns_common_test.py @@ -36,7 +36,7 @@ class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthen fake_file_path = None def setUp(self): - super(DNSAuthenticatorTest, self).setUp() + super().setUp() self.config = DNSAuthenticatorTest._FakeConfig() @@ -165,7 +165,7 @@ class CredentialsConfigurationTest(test_util.TempDirTestCase): class CredentialsConfigurationRequireTest(test_util.TempDirTestCase): def setUp(self): - super(CredentialsConfigurationRequireTest, self).setUp() + super().setUp() self.path = os.path.join(self.tempdir, 'file.ini') diff --git a/certbot/tests/plugins/enhancements_test.py b/certbot/tests/plugins/enhancements_test.py index a20a6864f..0aa1512b4 100644 --- a/certbot/tests/plugins/enhancements_test.py +++ b/certbot/tests/plugins/enhancements_test.py @@ -15,7 +15,7 @@ class EnhancementTest(test_util.ConfigTestCase): """Tests for new style enhancements in certbot.plugins.enhancements""" def setUp(self): - super(EnhancementTest, self).setUp() + super().setUp() self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement) diff --git a/certbot/tests/plugins/manual_test.py b/certbot/tests/plugins/manual_test.py index 0e552e0c5..08ab20456 100644 --- a/certbot/tests/plugins/manual_test.py +++ b/certbot/tests/plugins/manual_test.py @@ -19,7 +19,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): """Tests for certbot._internal.plugins.manual.Authenticator.""" def setUp(self): - super(AuthenticatorTest, self).setUp() + super().setUp() self.http_achall = acme_util.HTTP01_A self.dns_achall = acme_util.DNS01_A self.dns_achall_2 = acme_util.DNS01_A_2 diff --git a/certbot/tests/plugins/selection_test.py b/certbot/tests/plugins/selection_test.py index eed07ae76..7fcd213a0 100644 --- a/certbot/tests/plugins/selection_test.py +++ b/certbot/tests/plugins/selection_test.py @@ -160,7 +160,7 @@ class GetUnpreparedInstallerTest(test_util.ConfigTestCase): """Tests for certbot._internal.plugins.selection.get_unprepared_installer.""" def setUp(self): - super(GetUnpreparedInstallerTest, self).setUp() + super().setUp() self.mock_apache_fail_ep = mock.Mock( description_with_name="afail") self.mock_apache_fail_ep.check_name = lambda name: name == "afail" diff --git a/certbot/tests/plugins/standalone_test.py b/certbot/tests/plugins/standalone_test.py index 636b251de..5b3fffd9d 100644 --- a/certbot/tests/plugins/standalone_test.py +++ b/certbot/tests/plugins/standalone_test.py @@ -1,7 +1,6 @@ """Tests for certbot._internal.plugins.standalone.""" -# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi +import errno import socket -from socket import errno as socket_errors # type: ignore import unittest from typing import Dict, Set, Tuple @@ -106,8 +105,8 @@ class AuthenticatorTest(unittest.TestCase): @test_util.patch_get_utility() def test_perform_eaddrinuse_retry(self, mock_get_utility): mock_utility = mock_get_utility() - errno = socket_errors.EADDRINUSE - error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1) + encountered_errno = errno.EADDRINUSE + error = errors.StandaloneBindError(mock.MagicMock(errno=encountered_errno), -1) self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()] mock_yesno = mock_utility.yesno mock_yesno.return_value = True @@ -121,8 +120,8 @@ class AuthenticatorTest(unittest.TestCase): mock_yesno = mock_utility.yesno mock_yesno.return_value = False - errno = socket_errors.EADDRINUSE - self.assertRaises(errors.PluginError, self._fail_perform, errno) + encountered_errno = errno.EADDRINUSE + self.assertRaises(errors.PluginError, self._fail_perform, encountered_errno) self._assert_correct_yesno_call(mock_yesno) def _assert_correct_yesno_call(self, mock_yesno): @@ -131,16 +130,16 @@ class AuthenticatorTest(unittest.TestCase): self.assertFalse(yesno_kwargs.get("default", True)) def test_perform_eacces(self): - errno = socket_errors.EACCES - self.assertRaises(errors.PluginError, self._fail_perform, errno) + encountered_errno = errno.EACCES + self.assertRaises(errors.PluginError, self._fail_perform, encountered_errno) def test_perform_unexpected_socket_error(self): - errno = socket_errors.ENOTCONN + encountered_errno = errno.ENOTCONN self.assertRaises( - errors.StandaloneBindError, self._fail_perform, errno) + errors.StandaloneBindError, self._fail_perform, encountered_errno) - def _fail_perform(self, errno): - error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1) + def _fail_perform(self, encountered_errno): + error = errors.StandaloneBindError(mock.MagicMock(errno=encountered_errno), -1) self.auth.servers.run.side_effect = error self.auth.perform(self._get_achalls()) diff --git a/certbot/tests/plugins/storage_test.py b/certbot/tests/plugins/storage_test.py index 2999d306e..36159e44f 100644 --- a/certbot/tests/plugins/storage_test.py +++ b/certbot/tests/plugins/storage_test.py @@ -18,7 +18,7 @@ class PluginStorageTest(test_util.ConfigTestCase): """Test for certbot.plugins.storage.PluginStorage""" def setUp(self): - super(PluginStorageTest, self).setUp() + super().setUp() self.plugin_cls = common.Installer filesystem.mkdir(self.config.config_dir) with mock.patch("certbot.reverter.util"): @@ -31,7 +31,7 @@ class PluginStorageTest(test_util.ConfigTestCase): # When unable to read file that exists mock_open = mock.mock_open() mock_open.side_effect = IOError - self.plugin.storage.storagepath = os.path.join(self.config.config_dir, + self.plugin.storage._storagepath = os.path.join(self.config.config_dir, ".pluginstorage.json") with mock.patch("builtins.open", mock_open): with mock.patch('certbot.compat.os.path.isfile', return_value=True): @@ -67,7 +67,7 @@ class PluginStorageTest(test_util.ConfigTestCase): with mock.patch("certbot.plugins.storage.logger.error") as mock_log: # Set data as something that can't be serialized self.plugin.storage._initialized = True # pylint: disable=protected-access - self.plugin.storage.storagepath = "/tmp/whatever" + self.plugin.storage._storagepath = "/tmp/whatever" self.plugin.storage._data = self.plugin_cls # pylint: disable=protected-access self.assertRaises(errors.PluginStorageError, self.plugin.storage.save) @@ -80,6 +80,7 @@ class PluginStorageTest(test_util.ConfigTestCase): with mock.patch("certbot.plugins.storage.logger.error") as mock_log: self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access self.plugin.storage._initialized = True # pylint: disable=protected-access + self.plugin.storage._storagepath = "/tmp/whatever" self.assertRaises(errors.PluginStorageError, self.plugin.storage.save) self.assertTrue("Could not write" in mock_log.call_args[0][0]) diff --git a/certbot/tests/plugins/webroot_test.py b/certbot/tests/plugins/webroot_test.py index e6fbd8e88..5792924a9 100644 --- a/certbot/tests/plugins/webroot_test.py +++ b/certbot/tests/plugins/webroot_test.py @@ -141,7 +141,8 @@ class AuthenticatorTest(unittest.TestCase): f.write("thingimy") filesystem.chmod(self.path, 0o000) try: - open(permission_canary, "r") + with open(permission_canary, "r"): + pass print("Warning, running tests as root skips permissions tests...") except IOError: # ok, permissions work, test away... diff --git a/certbot/tests/renewupdater_test.py b/certbot/tests/renewupdater_test.py index b5ecddb5a..5137c81c3 100644 --- a/certbot/tests/renewupdater_test.py +++ b/certbot/tests/renewupdater_test.py @@ -17,7 +17,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): """Tests for interfaces.RenewDeployer and interfaces.GenericUpdater""" def setUp(self): - super(RenewUpdaterTest, self).setUp() + super().setUp() self.generic_updater = mock.MagicMock(spec=interfaces.GenericUpdater) self.generic_updater.restart = mock.MagicMock() self.renew_deployer = mock.MagicMock(spec=interfaces.RenewDeployer) diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index af01a9a1b..45479b16d 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -18,7 +18,7 @@ from certbot.tests import util as test_util class ReverterCheckpointLocalTest(test_util.ConfigTestCase): """Test the Reverter Class.""" def setUp(self): - super(ReverterCheckpointLocalTest, self).setUp() + super().setUp() from certbot.reverter import Reverter # Disable spurious errors... we are trying to test for them @@ -280,7 +280,7 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): class TestFullCheckpointsReverter(test_util.ConfigTestCase): """Tests functions having to deal with full checkpoints.""" def setUp(self): - super(TestFullCheckpointsReverter, self).setUp() + super().setUp() from certbot.reverter import Reverter # Disable spurious errors... logging.disable(logging.CRITICAL) diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 5557db76a..fcf65abf5 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -99,7 +99,7 @@ class BaseRenewableCertTest(test_util.ConfigTestCase): def setUp(self): from certbot._internal import storage - super(BaseRenewableCertTest, self).setUp() + super().setUp() # TODO: maybe provide NamespaceConfig.make_dirs? # TODO: main() should create those dirs, c.f. #902 @@ -846,7 +846,7 @@ class RenewableCertTests(BaseRenewableCertTest): class DeleteFilesTest(BaseRenewableCertTest): """Tests for certbot._internal.storage.delete_files""" def setUp(self): - super(DeleteFilesTest, self).setUp() + super().setUp() for kind in ALL_FOUR: kind_path = os.path.join(self.config.config_dir, "live", "example.org", @@ -936,7 +936,7 @@ class DeleteFilesTest(BaseRenewableCertTest): class CertPathForCertNameTest(BaseRenewableCertTest): """Test for certbot._internal.storage.cert_path_for_cert_name""" def setUp(self): - super(CertPathForCertNameTest, self).setUp() + super().setUp() self.config_file.write() self._write_out_ex_kinds() self.fullchain = os.path.join(self.config.config_dir, 'live', 'example.org', diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index abde665a5..2fbadaa80 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -108,7 +108,7 @@ class LockDirUntilExit(test_util.TempDirTestCase): return lock_dir_until_exit(*args, **kwargs) def setUp(self): - super(LockDirUntilExit, self).setUp() + super().setUp() # reset global state from other tests import certbot.util reload_module(certbot.util) @@ -164,7 +164,7 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): """ def setUp(self): - super(MakeOrVerifyDirTest, self).setUp() + super().setUp() self.path = os.path.join(self.tempdir, "foo") filesystem.mkdir(self.path, 0o600) @@ -196,7 +196,7 @@ class UniqueFileTest(test_util.TempDirTestCase): """Tests for certbot.util.unique_file.""" def setUp(self): - super(UniqueFileTest, self).setUp() + super().setUp() self.default_name = os.path.join(self.tempdir, "foo.txt") @@ -284,7 +284,7 @@ class SafelyRemoveTest(test_util.TempDirTestCase): """Tests for certbot.util.safely_remove.""" def setUp(self): - super(SafelyRemoveTest, self).setUp() + super().setUp() self.path = os.path.join(self.tempdir, "foo") diff --git a/letsencrypt-auto b/letsencrypt-auto index 24af0b4c3..c37c45596 100755 --- a/letsencrypt-auto +++ b/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.13.0" +LE_AUTO_VERSION="1.14.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -800,6 +800,7 @@ BootstrapMageiaCommon() { # packages BOOTSTRAP_VERSION is not set. if [ -f /etc/debian_version ]; then DEPRECATED_OS=1 + NO_SELF_UPGRADE=1 elif [ -f /etc/mageia-release ]; then # Mageia has both /etc/mageia-release and /etc/redhat-release DEPRECATED_OS=1 @@ -1488,18 +1489,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.13.0 \ - --hash=sha256:082eb732e1318bb9605afa7aea8db2c2f4c5029d523c73f24c6aa98f03caff76 \ - --hash=sha256:64cf41b57df7667d9d849fcaa9031a4f151788246733d1f4c3f37a5aa5e2f458 -acme==1.13.0 \ - --hash=sha256:93b6365c9425de03497a6b8aee1107814501d2974499b42e9bcc9a7378771143 \ - --hash=sha256:6b4257dfd6a6d5f01e8cd4f0b10422c17836bed7c67e9c5b0a0ad6c7d651c088 -certbot-apache==1.13.0 \ - --hash=sha256:36ed02ac7d2d91febee8dd3181ae9095b3f06434c9ed8959fbc6db24ab4da2e8 \ - --hash=sha256:4b5a16e80c1418e2edc05fc2578f522fb24974b2c13eb747cdfeef69e5bd5ae1 -certbot-nginx==1.13.0 \ - --hash=sha256:3ff271f65321b25c77a868af21f76f58754a7d61529ad565a1d66e29c711120f \ - --hash=sha256:9e972cc19c0fa9e5b7863da0423b156fbfb5623fd30b558fd2fd6d21c24c0b08 +certbot==1.14.0 \ + --hash=sha256:67b4d26ceaea6c7f8325d0d45169e7a165a2cabc7122c84bc971ba068ca19cca \ + --hash=sha256:959ea90c6bb8dca38eab9772722cb940972ef6afcd5f15deef08b3c3636841eb +acme==1.14.0 \ + --hash=sha256:4f48c41261202f1a389ec2986b2580b58f53e0d5a1ae2463b34318d78b87fc66 \ + --hash=sha256:61daccfb0343628cbbca551a7fc4c82482113952c21db3fe0c585b7c98fa1c35 +certbot-apache==1.14.0 \ + --hash=sha256:b757038db23db707c44630fecb46e99172bd791f0db5a8e623c0842613c4d3d9 \ + --hash=sha256:887fe4a21af2de1e5c2c9428bacba6eb7c1219257bc70f1a1d8447c8a321adb0 +certbot-nginx==1.14.0 \ + --hash=sha256:8916a815437988d6c192df9f035bb7a176eab20eee0956677b335d0698d243fb \ + --hash=sha256:cc2a8a0de56d9bb6b2efbda6c80c647dad8db2bb90675cac03ade94bd5fc8597 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index fbd16f2b0..c0cf63418 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAmA+sv4ACgkQTRfJlc2X -dfLG+ggAvwUCqy06UZd8jZhOZFUoi8nWHG2TMnm/4CW4G9PPCjsCoQplhaAaUCrR -PAv0vtsG1rBKWekICg6IBTzLVioH9xRPUkpfbVQhT1c1T/5CqMsFFXR5p9YAKRe7 -hlOb7VRN10bdCS4JThRPNhdWFdWKZXYKcIOObWA/FX2GacxMHuLwPpSsbt2NRffy -qz1ZOWxvr289aWEbZWfyBiI2bxQ7wlMEbZ/JLUXDe46ETDxzENu+c0x2109ha6m4 -wHmUS0r16ps/n9DueTZGJf3C26mU+cIB+LgNvOcibBo+0Ly7t+OiBHYbkFXS2KTQ -RbH4bPZrsduUOzhE8wIsSUIsVGDleQ== -=jYnL +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAmBsmUkACgkQTRfJlc2X +dfI7Bwf9FkNrf1HEh2G3uk1p+qLMd/s5kcVV2udK2FkRELee5nHlLZx2YmHA/8ID +gqsk8EsyRZNMX374nGrPm0syykdEsyVtMJTbHCEr+Ms3l54ZgE3HV6ywnhWSlAFo +Za50kdzhodBVTS5AEADbCKLKObVAWwO3fFKtKyv/iY29ykpHK0KSHCKRII3iQU7l +dnR6u35Z0wgfEmDxsH27K6uo0YepZaEL70qHHFk93MhCh9Z15rO17gRpsVzz7Z1j +YClI6h2K/VOfZtbkoQvoks7s+xd75Kjr3GNH+cznkJx8gNWSZLfkc1XX4Bjdm4GG +IWz3Ezy8tFg6PtITb7y+aIg75kWx4w== +=zEy4 -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index b0b25759f..9ddc7e076 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.14.0.dev0" +LE_AUTO_VERSION="1.15.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1489,18 +1489,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.13.0 \ - --hash=sha256:082eb732e1318bb9605afa7aea8db2c2f4c5029d523c73f24c6aa98f03caff76 \ - --hash=sha256:64cf41b57df7667d9d849fcaa9031a4f151788246733d1f4c3f37a5aa5e2f458 -acme==1.13.0 \ - --hash=sha256:93b6365c9425de03497a6b8aee1107814501d2974499b42e9bcc9a7378771143 \ - --hash=sha256:6b4257dfd6a6d5f01e8cd4f0b10422c17836bed7c67e9c5b0a0ad6c7d651c088 -certbot-apache==1.13.0 \ - --hash=sha256:36ed02ac7d2d91febee8dd3181ae9095b3f06434c9ed8959fbc6db24ab4da2e8 \ - --hash=sha256:4b5a16e80c1418e2edc05fc2578f522fb24974b2c13eb747cdfeef69e5bd5ae1 -certbot-nginx==1.13.0 \ - --hash=sha256:3ff271f65321b25c77a868af21f76f58754a7d61529ad565a1d66e29c711120f \ - --hash=sha256:9e972cc19c0fa9e5b7863da0423b156fbfb5623fd30b558fd2fd6d21c24c0b08 +certbot==1.14.0 \ + --hash=sha256:67b4d26ceaea6c7f8325d0d45169e7a165a2cabc7122c84bc971ba068ca19cca \ + --hash=sha256:959ea90c6bb8dca38eab9772722cb940972ef6afcd5f15deef08b3c3636841eb +acme==1.14.0 \ + --hash=sha256:4f48c41261202f1a389ec2986b2580b58f53e0d5a1ae2463b34318d78b87fc66 \ + --hash=sha256:61daccfb0343628cbbca551a7fc4c82482113952c21db3fe0c585b7c98fa1c35 +certbot-apache==1.14.0 \ + --hash=sha256:b757038db23db707c44630fecb46e99172bd791f0db5a8e623c0842613c4d3d9 \ + --hash=sha256:887fe4a21af2de1e5c2c9428bacba6eb7c1219257bc70f1a1d8447c8a321adb0 +certbot-nginx==1.14.0 \ + --hash=sha256:8916a815437988d6c192df9f035bb7a176eab20eee0956677b335d0698d243fb \ + --hash=sha256:cc2a8a0de56d9bb6b2efbda6c80c647dad8db2bb90675cac03ade94bd5fc8597 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 3516f77b0..b297bdfb3 100644 --- a/letsencrypt-auto-source/letsencrypt-auto.sig +++ b/letsencrypt-auto-source/letsencrypt-auto.sig @@ -1,3 +1 @@ -_̊ПN*JsQj SllY3Q]x+/V`$4xM晾UăSPl Hb a<{p~`Ќ&a1E@;RNiLJ@pwk! &, letsencrypt-auto-source/pieces/certbot-requirements.txt deactivate -# there should be one requirement specifier and two hashes for each subpackage -expected_count=$(expr $(echo $SUBPKGS_IN_AUTO | wc -w) \* 3) -if ! wc -l letsencrypt-auto-source/pieces/certbot-requirements.txt | grep -qE "^\s*$expected_count " ; then - echo Unexpected pip hash output - exit 1 -fi -# ensure we have the latest built version of leauto -letsencrypt-auto-source/build.py - -# Now we have to sign the built version of leauto. -SignLEAuto() { - yubico-piv-tool -a verify-pin --sign -s 9c -i letsencrypt-auto-source/letsencrypt-auto -o letsencrypt-auto-source/letsencrypt-auto.sig -} - -# Loop until letsencrypt-auto is signed correctly. -SignLEAuto || true -while ! openssl dgst -sha256 -verify $RELEASE_OPENSSL_PUBKEY -signature \ - letsencrypt-auto-source/letsencrypt-auto.sig \ - letsencrypt-auto-source/letsencrypt-auto ; do - echo "The signature on letsencrypt-auto is not correct." - read -p "Would you like this script to try and sign it again [Y/n]?" response - case $response in - [yY][eE][sS]|[yY]|"") - SignLEAuto || true;; - *) - ;; - esac -done - -# This signature is not quite as strong, but easier for people to verify out of band -while ! gpg2 -u "$RELEASE_GPG_KEY" --detach-sign --armor --sign --digest-algo sha256 letsencrypt-auto-source/letsencrypt-auto; do - echo "Unable to sign letsencrypt-auto using $RELEASE_KEY." - echo "Make sure your OpenPGP card is in your computer if you are using one." - echo "You may need to take the card out and put it back in again." - read -p "Press enter to try signing again." -done -# We can't rename the openssl letsencrypt-auto.sig for compatibility reasons, -# but we can use the right name for certbot-auto.asc from day one -mv letsencrypt-auto-source/letsencrypt-auto.asc letsencrypt-auto-source/certbot-auto.asc - -# copy leauto to the root, overwriting the previous release version -cp -p letsencrypt-auto-source/letsencrypt-auto certbot-auto -cp -p letsencrypt-auto-source/letsencrypt-auto letsencrypt-auto - -git add certbot-auto letsencrypt-auto letsencrypt-auto-source certbot/docs/cli-help.txt +git add certbot/docs/cli-help.txt while ! git commit --gpg-sign="$RELEASE_GPG_KEY" -m "Release $version"; do echo "Unable to sign the release commit using git." echo "You may have to configure git to use gpg2 by running:" @@ -283,15 +229,11 @@ git commit -m "Add contents to certbot/CHANGELOG.md for next version" echo "New root: $root" echo "Test commands (in the letstest repo):" -echo 'python multitester.py auto_targets.yaml $AWS_KEY $USERNAME scripts/test_leauto_upgrades.sh --alt_pip $YOUR_PIP_REPO --branch public-beta' -echo 'python multitester.py auto_targets.yaml $AWK_KEY $USERNAME scripts/test_letsencrypt_auto_certonly_standalone.sh --branch candidate-0.1.1' echo 'python multitester.py --saveinstances targets.yaml $AWS_KEY $USERNAME scripts/test_apache2.sh' echo "In order to upload packages run the following command:" echo twine upload "$root/dist.$version/*/*" if [ "$RELEASE_BRANCH" = candidate-"$version" ] ; then SetVersion "$nextversion".dev0 - letsencrypt-auto-source/build.py - git add letsencrypt-auto-source/letsencrypt-auto git commit -m "Bump version to $nextversion" fi diff --git a/tools/eff-pubkey.pem b/tools/eff-pubkey.pem deleted file mode 100644 index fe6c2f5bb..000000000 --- a/tools/eff-pubkey.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6MR8W/galdxnpGqBsYbq -OzQb2eyW15YFjDDEMI0ZOzt8f504obNs920lDnpPD2/KqgsfjOgw2K7xWDJIj/18 -xUvWPk3LDkrnokNiRkA3KOx3W6fHycKL+zID7zy+xZYBuh2fLyQtWV1VGQ45iNRp -9+Zo7rH86cdfgkdnWTlNSHyTLW9NbXvyv/E12bppPcEvgCTAQXgnDVJ0/sqmeiij -n9tTFh03aM+R2V/21h8aTraAS24qiPCz6gkmYGC8yr6mglcnNoYbsLNYZ69zF1XH -cXPduCPdPdfLlzVlKK1/U7hkA28eG3BIAMh6uJYBRJTpiGgaGdPd7YekUB8S6cy+ -CQIDAQAB ------END PUBLIC KEY----- diff --git a/tools/pinning/pyproject.toml b/tools/pinning/pyproject.toml index 0107733d5..34fea18a6 100644 --- a/tools/pinning/pyproject.toml +++ b/tools/pinning/pyproject.toml @@ -35,6 +35,20 @@ acme = {path = "../../acme", extras = ["dev", "docs"]} windows-installer = {path = "../../windows-installer"} # Extra dependencies +# As of writing this, cython is a build dependency of pyyaml. Since there +# doesn't appear to be a good way to automatically track down and pin build +# dependencies in Python (see +# https://discuss.python.org/t/how-to-pin-build-dependencies/8238), we list it +# as a dependency here to ensure a version of cython is pinned for extra +# stability. +cython = "*" +# We install mock in our "external-mock" tox environment to test that we didn't +# break Certbot's test API which used to always use mock objects from the 3rd +# party mock library. We list the mock dependency here so that is pinned, but +# we don't depend on it in Certbot to avoid installing mock when it's not +# needed. This dependency can be removed here once Certbot's support for the +# 3rd party mock library has been dropped. +mock = "*" # Upgrading coverage, pylint, pytest, and some of pytest's plugins causes many # test failures so let's pin these packages back for now. coverage = "4.5.4" diff --git a/tools/requirements.txt b/tools/requirements.txt index 707cc64b9..b662744c6 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -18,8 +18,8 @@ backcall==0.2.0 bcrypt==3.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" beautifulsoup4==4.9.3; python_version >= "3.6" and python_version < "4.0" bleach==3.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" -boto3==1.17.42; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -botocore==1.20.42; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" +boto3==1.17.53; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" +botocore==1.20.53; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" cachecontrol==0.12.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" cached-property==1.5.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" cachetools==4.2.1; python_version >= "3.5" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") @@ -36,7 +36,8 @@ configobj==5.0.6; python_version >= "3.6" coverage==4.5.4; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0" and python_version < "4") crashtest==0.3.1; python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") cryptography==3.4.7; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "linux" or python_full_version >= "3.5.0" and python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "linux" -decorator==4.4.2; python_version == "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.2.0" +cython==0.29.23; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") +decorator==5.0.7 deprecated==1.2.12; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" distlib==0.3.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" distro==1.5.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" @@ -51,9 +52,9 @@ execnet==1.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or pyt filelock==3.0.12; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "4.0" future==0.18.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" google-api-core==1.26.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -google-api-python-client==2.1.0; python_version >= "3.6" +google-api-python-client==2.2.0; python_version >= "3.6" google-auth-httplib2==0.1.0; python_version >= "3.6" -google-auth==1.28.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" +google-auth==1.29.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" googleapis-common-protos==1.53.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" html5lib==1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" httplib2==0.19.1; python_version >= "3.6" @@ -80,6 +81,7 @@ lazy-object-proxy==1.4.3; python_version >= "3.6" and python_full_version < "3.0 lockfile==0.12.2 markupsafe==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" mccabe==0.6.1; python_version >= "3.6" +mock==4.0.3; python_version >= "3.6" msgpack==1.0.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" msrest==0.6.21; python_version >= "3.6" mypy-extensions==0.4.3; python_version >= "3.6" @@ -96,10 +98,10 @@ pickleshare==0.7.5 pkginfo==1.7.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" pluggy==0.13.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" ply==3.11; python_version >= "3.6" -poetry-core==1.0.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" -poetry==1.1.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" +poetry-core==1.0.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" +poetry==1.1.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" prompt-toolkit==3.0.3 -protobuf==3.15.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" +protobuf==3.15.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" ptyprocess==0.7.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pyasn1-modules==0.2.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" @@ -124,7 +126,7 @@ pytest==3.2.5 python-augeas==0.5.0 python-dateutil==2.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" python-digitalocean==1.16.0; python_version >= "3.6" -python-dotenv==0.16.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +python-dotenv==0.17.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pytz==2021.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.6.0" pywin32-ctypes==0.2.0; python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "win32" pywin32==300; sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") @@ -138,14 +140,14 @@ requests-toolbelt==0.9.1; python_version >= "3.6" and python_full_version < "3.0 requests==2.25.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_full_version >= "3.6.0" and python_version < "4.0" rfc3986==1.4.0; python_version >= "3.6" rsa==4.7.2; python_version >= "3.6" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -s3transfer==0.3.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" +s3transfer==0.3.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" secretstorage==3.3.1; python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "linux" shellingham==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" six==1.15.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" snowballstemmer==2.1.0; python_version >= "3.6" soupsieve==2.2.1; python_version >= "3.6" -sphinx-rtd-theme==0.5.1; python_version >= "3.6" -sphinx==3.5.3; python_version >= "3.6" +sphinx-rtd-theme==0.5.2; python_version >= "3.6" +sphinx==3.5.4; python_version >= "3.6" sphinxcontrib-applehelp==1.0.2; python_version >= "3.6" sphinxcontrib-devhelp==1.0.2; python_version >= "3.6" sphinxcontrib-htmlhelp==1.0.3; python_version >= "3.6" @@ -157,10 +159,10 @@ tldextract==3.1.0; python_version >= "3.6" and python_version < "4.0" toml==0.10.2; python_version == "3.6" and python_full_version < "3.0.0" or python_version > "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.5.0" or python_version > "3.6" and python_full_version >= "3.5.0" tomlkit==0.7.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" tox==3.23.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" -tqdm==4.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" +tqdm==4.60.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" traitlets==4.3.3 twine==3.3.0; python_version >= "3.6" -typed-ast==1.4.2; implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6" +typed-ast==1.4.3; implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6" typing-extensions==3.7.4.3; python_version >= "3.6" uritemplate==3.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" urllib3==1.26.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.6" @@ -174,7 +176,7 @@ zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" and pytho zope.component==5.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" zope.event==4.5.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" zope.hookable==5.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -zope.interface==5.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +zope.interface==5.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pip==20.2.4 setuptools==54.1.2 wheel==0.35.1 diff --git a/tools/snap/build_remote.py b/tools/snap/build_remote.py index b12799dc3..91854e9e0 100755 --- a/tools/snap/build_remote.py +++ b/tools/snap/build_remote.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse import datetime +import functools import glob from multiprocessing import Manager from multiprocessing import Pool @@ -28,21 +29,34 @@ CERTBOT_DIR = dirname(dirname(dirname(realpath(__file__)))) PLUGINS = [basename(path) for path in glob.glob(join(CERTBOT_DIR, 'certbot-dns-*'))] +# In Python, stdout and stderr are buffered in each process by default. When +# printing output from multiple processes, this can cause delays in printing +# output with lines from different processes being interleaved depending +# on when the output for that process is flushed. To prevent this, we override +# print so that it always flushes its output. Disabling output buffering can +# also be done through command line flags or environment variables set when the +# Python process starts, but this approach was taken instead to ensure +# consistent behavior regardless of how the script is invoked. +print = functools.partial(print, flush=True) + + +def _snap_log_name(target: str, arch: str): + return f'{target}_{arch}.txt' + + def _execute_build( target: str, archs: Set[str], status: Dict[str, Dict[str, str]], workspace: str) -> Tuple[int, List[str]]: - temp_workspace = None - try: - # Snapcraft remote-build has a recover feature, that will make it reconnect to an existing - # build on Launchpad if possible. However, the signature used to retrieve a potential - # build is not based on the content of the sources used to build a snap, but on a hash - # of the snapcraft current working directory (the path itself, not the content). - # It means that every build started from /my/path/to/certbot will always be considered - # as the same build, whatever the actual sources are. - # To circumvent this, we create a temporary folder and use it as a workspace to build - # the snap: this path is random, making the recover feature effectively noop. - temp_workspace = tempfile.mkdtemp() + # Snapcraft remote-build has a recover feature, that will make it reconnect to an existing + # build on Launchpad if possible. However, the signature used to retrieve a potential + # build is not based on the content of the sources used to build a snap, but on a hash + # of the snapcraft current working directory (the path itself, not the content). + # It means that every build started from /my/path/to/certbot will always be considered + # as the same build, whatever the actual sources are. + # To circumvent this, we create a temporary folder and use it as a workspace to build + # the snap: this path is random, making the recover feature effectively noop. + with tempfile.TemporaryDirectory() as temp_workspace: ignore = None if target == 'certbot': ignore = shutil.ignore_patterns(".git", "venv*", ".tox") @@ -71,20 +85,18 @@ def _execute_build( for path in glob.glob(join(temp_workspace, '*.snap')): shutil.copy(path, workspace) + for arch in archs: + log_name = _snap_log_name(target, arch) + log_path = join(temp_workspace, log_name) + if exists(log_path): + shutil.copy(log_path, workspace) return process_state, process_output - except BaseException as e: - print(e) - sys.stdout.flush() - raise e - finally: - if temp_workspace: - shutil.rmtree(temp_workspace, ignore_errors=True) def _build_snap( target: str, archs: Set[str], status: Dict[str, Dict[str, str]], - running: Dict[str, bool], lock: Lock) -> Dict[str, str]: + running: Dict[str, bool], output_lock: Lock) -> bool: status[target] = {arch: '...' for arch in archs} if target == 'certbot': @@ -92,24 +104,19 @@ def _build_snap( else: workspace = join(CERTBOT_DIR, target) + build_success = False retry = 3 while retry: exit_code, process_output = _execute_build(target, archs, status, workspace) + with output_lock: + print(f'Build {target} for {",".join(archs)} (attempt {4-retry}/3) ended with ' + f'exit code {exit_code}.') - print(f'Build {target} for {",".join(archs)} (attempt {4-retry}/3) ended with ' - f'exit code {exit_code}.') - sys.stdout.flush() - - with lock: - dump_output = exit_code != 0 failed_archs = [arch for arch in archs if status[target][arch] != 'Successfully built'] - if any(arch for arch in archs if status[target][arch] == 'Chroot problem'): - print('Some builds failed with the status "Chroot problem".') - print('This status is known to make any future build fail until either ' - 'the source code changes or the build on Launchpad is deleted.') - print('Please fix the build appropriately before trying a new one.') - # It is useless to retry in this situation. - retry = 0 + # If the command failed or any architecture wasn't built + # successfully, let's try to print all the output about the problem + # that we can. + dump_output = exit_code != 0 or failed_archs if exit_code == 0 and not failed_archs: # We expect to have all target snaps available, or something bad happened. snaps_list = glob.glob(join(workspace, '*.snap')) @@ -118,16 +125,12 @@ def _build_snap( f'(current list: {snaps_list}).') dump_output = True else: + build_success = True break - if failed_archs: - # We expect each failed build to have a log file, or something bad happened. - for arch in failed_archs: - if not exists(join(workspace, f'{target}_{arch}.txt')): - dump_output = True - print(f'Missing output on a failed build {target} for {arch}.') if dump_output: print(f'Dumping snapcraft remote-build output build for {target}:') print('\n'.join(process_output)) + _dump_failed_build_logs(target, archs, status, workspace) # Retry the remote build if it has been interrupted (non zero status code) # or if some builds have failed. @@ -135,7 +138,7 @@ def _build_snap( running[target] = False - return {target: workspace} + return build_success def _extract_state(project: str, output: str, status: Dict[str, Dict[str, str]]) -> None: @@ -167,53 +170,45 @@ def _dump_status_helper(archs: Set[str], status: Dict[str, Dict[str, str]]) -> N print(f'|{"-" * 26}' * len(headers)) print() - sys.stdout.flush() - def _dump_status( archs: Set[str], status: Dict[str, Dict[str, str]], - running: Dict[str, bool]) -> None: + running: Dict[str, bool], output_lock: Lock) -> None: while any(running.values()): - print(f'Remote build status at {datetime.datetime.now()}') - _dump_status_helper(archs, status) + with output_lock: + print(f'Remote build status at {datetime.datetime.now()}') + _dump_status_helper(archs, status) time.sleep(10) -def _dump_results( - targets: Set[str], archs: Set[str], status: Dict[str, Dict[str, str]], - workspaces: Dict[str, str]) -> bool: - failures = False - for target in targets: - for arch in archs: - result = status[target][arch] +def _dump_failed_build_logs( + target: str, archs: Set[str], status: Dict[str, Dict[str, str]], + workspace: str) -> None: + for arch in archs: + result = status[target][arch] - if result != 'Successfully built': - failures = True + if result != 'Successfully built': + failures = True - build_output_path = join(workspaces[target], f'{target}_{arch}.txt') - if not exists(build_output_path): - build_output = f'No output has been dumped by snapcraft remote-build.' - else: - with open(join(workspaces[target], f'{target}_{arch}.txt')) as file_h: - build_output = file_h.read() + build_output_name = _snap_log_name(target, arch) + build_output_path = join(workspace, build_output_name) + if not exists(build_output_path): + build_output = f'No output has been dumped by snapcraft remote-build.' + else: + with open(build_output_path) as file_h: + build_output = file_h.read() - print(f'Output for failed build target={target} arch={arch}') - print('-------------------------------------------') - print(build_output) - print('-------------------------------------------') - print() + print(f'Output for failed build target={target} arch={arch}') + print('-------------------------------------------') + print(build_output) + print('-------------------------------------------') + print() - if not failures: - print('All builds succeeded.') - else: - print('Some builds failed.') - print() +def _dump_results(archs: Set[str], status: Dict[str, Dict[str, str]]) -> None: print(f'Results for remote build finished at {datetime.datetime.now()}') _dump_status_helper(archs, status) - return failures - def main(): parser = argparse.ArgumentParser() @@ -252,12 +247,14 @@ def main(): with manager, pool: status: Dict[str, Dict[str, str]] = manager.dict() running = manager.dict({target: True for target in targets}) - lock = manager.Lock() + # While multiple processes are running, this lock should be acquired + # before printing output. + output_lock = manager.Lock() - async_results = [pool.apply_async(_build_snap, (target, archs, status, running, lock)) + async_results = [pool.apply_async(_build_snap, (target, archs, status, running, output_lock)) for target in targets] - process = Process(target=_dump_status, args=(archs, status, running)) + process = Process(target=_dump_status, args=(archs, status, running, output_lock)) process.start() try: @@ -266,11 +263,16 @@ def main(): if process.is_alive(): raise ValueError(f"Timeout out reached ({args.timeout} seconds) during the build!") - workspaces = {} + build_success = True for async_result in async_results: - workspaces.update(async_result.get()) + if not async_result.get(): + build_success = False - if _dump_results(targets, archs, status, workspaces): + _dump_results(archs, status) + if build_success: + print('All builds succeeded.') + else: + print('Some builds failed.') raise ValueError("There were failures during the build!") finally: process.terminate() diff --git a/tox.ini b/tox.ini index 18d0d7db3..ada7002da 100644 --- a/tox.ini +++ b/tox.ini @@ -156,7 +156,7 @@ commands = basepython = python3 commands = {[base]install_packages} - mypy --no-strict-optional {[base]source_paths} + mypy {[base]source_paths} [testenv:apacheconftest] commands = diff --git a/windows-installer/windows_installer/construct.py b/windows-installer/windows_installer/construct.py index 983923049..91df6b714 100644 --- a/windows-installer/windows_installer/construct.py +++ b/windows-installer/windows_installer/construct.py @@ -7,7 +7,7 @@ import subprocess import sys import time -PYTHON_VERSION = (3, 8, 8) +PYTHON_VERSION = (3, 8, 9) PYTHON_BITNESS = 32 NSIS_VERSION = '3.06.1'