From fc7e5e8e6060d9e0df2e704a20103d5c0f456925 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 13 Feb 2020 22:56:16 +0100 Subject: [PATCH 1/8] Remove useless pylint error suppression directives (#7657) As pylint is evolving, it improves its accuracy, and several pylint error suppression (`# pylint: disable=ERROR) added in certbot codebase months or years ago are not needed anymore to make it happy. There is a (disabled by default) pylint error to detect the useless suppressions (pylint-ception: `useless-suppression`). It is not working perfectly (it has also false-positives ...) but it is a good start to clean the codebase. This PR removes several of these useless suppressions as detected by the current pylint version we use. * Remove useless suppress * Remove useless lines --- acme/acme/challenges.py | 2 +- acme/acme/client.py | 14 ++++++------ acme/acme/crypto_util.py | 12 +++++----- acme/acme/jws.py | 4 ++-- acme/acme/magic_typing.py | 1 - acme/acme/messages.py | 7 +++--- acme/acme/standalone.py | 16 +++++--------- .../certbot_apache/_internal/configurator.py | 20 ++++++++--------- .../certbot_apache/_internal/entrypoint.py | 2 +- .../certbot_apache/_internal/http_01.py | 4 ++-- .../certbot_apache/_internal/obj.py | 2 +- .../_internal/override_centos.py | 2 +- .../certbot_apache/_internal/parser.py | 10 ++++----- .../configurators/nginx/common.py | 2 +- .../certbot_compatibility_test/test_driver.py | 4 ++-- .../certbot_compatibility_test/validator.py | 3 +-- .../_internal/dns_cloudflare.py | 2 +- .../_internal/dns_cloudxns.py | 2 +- .../_internal/dns_digitalocean.py | 2 +- .../_internal/dns_dnsimple.py | 2 +- .../_internal/dns_dnsmadeeasy.py | 2 +- .../_internal/dns_gehirn.py | 2 +- .../_internal/dns_google.py | 8 +++---- .../_internal/dns_linode.py | 2 +- .../_internal/dns_luadns.py | 2 +- .../certbot_dns_nsone/_internal/dns_nsone.py | 2 +- .../certbot_dns_ovh/_internal/dns_ovh.py | 2 +- .../_internal/dns_rfc2136.py | 2 +- .../_internal/dns_route53.py | 10 ++++----- .../_internal/dns_sakuracloud.py | 2 +- .../certbot_nginx/_internal/configurator.py | 14 ++++++------ .../certbot_nginx/_internal/http_01.py | 2 +- .../certbot_nginx/_internal/nginxparser.py | 1 - .../certbot_nginx/_internal/parser.py | 11 +++++----- .../certbot_nginx/_internal/parser_obj.py | 2 +- certbot/certbot/_internal/auth_handler.py | 6 ++--- certbot/certbot/_internal/cert_manager.py | 2 +- certbot/certbot/_internal/cli.py | 8 +++---- certbot/certbot/_internal/client.py | 6 ++--- certbot/certbot/_internal/configuration.py | 18 +++++++-------- certbot/certbot/_internal/error_handler.py | 10 ++++----- certbot/certbot/_internal/hooks.py | 4 ++-- certbot/certbot/_internal/lock.py | 10 ++++----- certbot/certbot/_internal/main.py | 2 +- certbot/certbot/_internal/plugins/disco.py | 5 +---- certbot/certbot/_internal/plugins/manual.py | 12 +++++----- certbot/certbot/_internal/plugins/null.py | 2 +- .../certbot/_internal/plugins/standalone.py | 22 +++++++++---------- certbot/certbot/_internal/plugins/webroot.py | 20 ++++++++--------- certbot/certbot/_internal/renewal.py | 2 +- certbot/certbot/_internal/reporter.py | 2 +- certbot/certbot/_internal/storage.py | 2 +- certbot/certbot/compat/_path.py | 2 +- certbot/certbot/compat/filesystem.py | 7 +++--- certbot/certbot/compat/misc.py | 2 +- certbot/certbot/crypto_util.py | 2 +- certbot/certbot/display/util.py | 1 - certbot/certbot/interfaces.py | 2 +- certbot/certbot/ocsp.py | 6 ++--- certbot/certbot/plugins/common.py | 3 +-- certbot/certbot/plugins/dns_common.py | 8 +++---- certbot/certbot/plugins/dns_common_lexicon.py | 6 ++--- certbot/certbot/plugins/enhancements.py | 6 ++--- certbot/certbot/plugins/storage.py | 4 ++-- certbot/certbot/reverter.py | 6 ++--- certbot/certbot/tests/acme_util.py | 3 +-- certbot/certbot/tests/util.py | 2 +- certbot/certbot/util.py | 8 +++---- letshelp-certbot/letshelp_certbot/apache.py | 2 +- .../letshelp_certbot/magic_typing.py | 1 - .../letshelp_certbot/magic_typing_test.py | 4 ++-- 71 files changed, 183 insertions(+), 202 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 39c8d6269..f3fb19b42 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -54,7 +54,7 @@ class UnrecognizedChallenge(Challenge): object.__setattr__(self, "jobj", jobj) def to_partial_json(self): - return self.jobj # pylint: disable=no-member + return self.jobj @classmethod def from_json(cls, jobj): diff --git a/acme/acme/client.py b/acme/acme/client.py index f48ff40b2..3e03748b5 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -15,16 +15,16 @@ import requests from requests.adapters import HTTPAdapter from requests_toolbelt.adapters.source import SourceAddressAdapter import six -from six.moves import http_client # pylint: disable=import-error +from six.moves import http_client from acme import crypto_util from acme import errors from acme import jws from acme import messages -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Text # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set +from acme.magic_typing import Text logger = logging.getLogger(__name__) @@ -36,7 +36,7 @@ if sys.version_info < (2, 7, 9): # pragma: no cover try: requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() # type: ignore except AttributeError: - import urllib3.contrib.pyopenssl # pylint: disable=import-error + import urllib3.contrib.pyopenssl urllib3.contrib.pyopenssl.inject_into_urllib3() DEFAULT_NETWORK_TIMEOUT = 45 @@ -666,7 +666,7 @@ class ClientV2(ClientBase): response = self._post(self.directory['newOrder'], order) body = messages.Order.from_json(response.json()) authorizations = [] - for url in body.authorizations: # pylint: disable=not-an-iterable + for url in body.authorizations: authorizations.append(self._authzr_from_response(self._post_as_get(url), uri=url)) return messages.OrderResource( body=body, diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 66dfc738c..dc8fedad0 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -11,10 +11,9 @@ from OpenSSL import crypto from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 from acme import errors -from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Callable +from acme.magic_typing import Tuple +from acme.magic_typing import Union logger = logging.getLogger(__name__) @@ -74,7 +73,7 @@ class SSLSocket(object): class FakeConnection(object): """Fake OpenSSL.SSL.Connection.""" - # pylint: disable=missing-docstring + # pylint: disable=missing-function-docstring def __init__(self, connection): self._wrapped = connection @@ -86,7 +85,7 @@ class SSLSocket(object): # OpenSSL.SSL.Connection.shutdown doesn't accept any args return self._wrapped.shutdown() - def accept(self): # pylint: disable=missing-docstring + def accept(self): # pylint: disable=missing-function-docstring sock, addr = self.sock.accept() context = SSL.Context(self.method) @@ -298,7 +297,6 @@ def dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM): def _dump_cert(cert): if isinstance(cert, jose.ComparableX509): - # pylint: disable=protected-access cert = cert.wrapped return crypto.dump_certificate(filetype, cert) diff --git a/acme/acme/jws.py b/acme/acme/jws.py index 894e69f3d..9128f56b3 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -15,7 +15,7 @@ class Header(jose.Header): url = jose.Field('url', omitempty=True) @nonce.decoder - def nonce(value): # pylint: disable=missing-docstring,no-self-argument + def nonce(value): # pylint: disable=no-self-argument,missing-function-docstring try: return jose.decode_b64jose(value) except jose.DeserializationError as error: @@ -25,7 +25,7 @@ class Header(jose.Header): class Signature(jose.Signature): """ACME-specific Signature. Uses ACME-specific Header for customer fields.""" - __slots__ = jose.Signature._orig_slots # pylint: disable=no-member + __slots__ = jose.Signature._orig_slots # TODO: decoder/encoder should accept cls? Otherwise, subclassing # JSONObjectWithFields is tricky... diff --git a/acme/acme/magic_typing.py b/acme/acme/magic_typing.py index 5a6358c69..d6b1ff056 100644 --- a/acme/acme/magic_typing.py +++ b/acme/acme/magic_typing.py @@ -10,7 +10,6 @@ class TypingClass(object): try: # mypy doesn't respect modifying sys.modules from typing import * # pylint: disable=wildcard-import, unused-wildcard-import - # pylint: disable=unused-import from typing import Collection, IO # type: ignore # pylint: enable=unused-import except ImportError: diff --git a/acme/acme/messages.py b/acme/acme/messages.py index e82d12890..f8f4bfbe7 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -11,7 +11,7 @@ from acme import jws from acme import util try: - from collections.abc import Hashable # pylint: disable=no-name-in-module + from collections.abc import Hashable except ImportError: # pragma: no cover from collections import Hashable @@ -460,7 +460,6 @@ class ChallengeResource(Resource): @property def uri(self): """The URL of the challenge body.""" - # pylint: disable=function-redefined,no-member return self.body.uri @@ -488,7 +487,7 @@ class Authorization(ResourceBody): wildcard = jose.Field('wildcard', omitempty=True) @challenges.decoder - def challenges(value): # pylint: disable=missing-docstring,no-self-argument + def challenges(value): # pylint: disable=no-self-argument,missing-function-docstring return tuple(ChallengeBody.from_json(chall) for chall in value) @property @@ -585,7 +584,7 @@ class Order(ResourceBody): error = jose.Field('error', omitempty=True, decoder=Error.from_json) @identifiers.decoder - def identifiers(value): # pylint: disable=missing-docstring,no-self-argument + def identifiers(value): # pylint: disable=no-self-argument,missing-function-docstring return tuple(Identifier.from_json(identifier) for identifier in value) class OrderResource(ResourceWithURI): diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index cf0da4e86..236f2c234 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -5,19 +5,16 @@ import logging import socket import threading -from six.moves import BaseHTTPServer # type: ignore # pylint: disable=import-error -from six.moves import http_client # pylint: disable=import-error -from six.moves import socketserver # type: ignore # pylint: disable=import-error +from six.moves import BaseHTTPServer # type: ignore +from six.moves import http_client +from six.moves import socketserver # type: ignore from acme import challenges from acme import crypto_util -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List logger = logging.getLogger(__name__) -# six.moves.* | pylint: disable=no-member,attribute-defined-outside-init -# pylint: disable=no-init - class TLSServer(socketserver.TCPServer): """Generic TLS Server.""" @@ -30,7 +27,6 @@ class TLSServer(socketserver.TCPServer): self.address_family = socket.AF_INET self.certs = kwargs.pop("certs", {}) self.method = kwargs.pop( - # pylint: disable=protected-access "method", crypto_util._DEFAULT_SSL_METHOD) self.allow_reuse_address = kwargs.pop("allow_reuse_address", True) socketserver.TCPServer.__init__(self, *args, **kwargs) @@ -39,7 +35,7 @@ class TLSServer(socketserver.TCPServer): self.socket = crypto_util.SSLSocket( self.socket, certs=self.certs, method=self.method) - def server_bind(self): # pylint: disable=missing-docstring + def server_bind(self): self._wrap_sock() return socketserver.TCPServer.server_bind(self) @@ -178,7 +174,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.log_message("Incoming request") BaseHTTPServer.BaseHTTPRequestHandler.handle(self) - def do_GET(self): # pylint: disable=invalid-name,missing-docstring + def do_GET(self): # pylint: disable=invalid-name,missing-function-docstring if self.path == "/": self.handle_index() elif self.path.startswith("/" + challenges.HTTP01.URI_ROOT_PATH): diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index e9ed1f8ab..465237590 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -14,11 +14,11 @@ import zope.component import zope.interface from acme import challenges -from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import DefaultDict +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set +from acme.magic_typing import Union from certbot import errors from certbot import interfaces from certbot import util @@ -792,7 +792,7 @@ class ApacheConfigurator(common.Installer): return util.get_filtered_names(all_names) - def get_name_from_ip(self, addr): # pylint: disable=no-self-use + def get_name_from_ip(self, addr): """Returns a reverse dns name if available. :param addr: IP Address @@ -1726,7 +1726,7 @@ class ApacheConfigurator(common.Installer): ###################################################################### # Enhancements ###################################################################### - def supported_enhancements(self): # pylint: disable=no-self-use + def supported_enhancements(self): """Returns currently supported enhancements.""" return ["redirect", "ensure-http-header", "staple-ocsp"] @@ -2292,7 +2292,7 @@ class ApacheConfigurator(common.Installer): vhost.enabled = True return - def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument + def enable_mod(self, mod_name, temp=False): """Enables module in Apache. Both enables and reloads Apache so module is active. @@ -2350,7 +2350,7 @@ class ApacheConfigurator(common.Installer): error = str(err) raise errors.MisconfigurationError(error) - def config_test(self): # pylint: disable=no-self-use + def config_test(self): """Check the configuration of Apache for errors. :raises .errors.MisconfigurationError: If config_test fails @@ -2400,7 +2400,7 @@ class ApacheConfigurator(common.Installer): ########################################################################### # Challenges Section ########################################################################### - def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use + def get_chall_pref(self, unused_domain): """Return list of challenge preferences.""" return [challenges.HTTP01] diff --git a/certbot-apache/certbot_apache/_internal/entrypoint.py b/certbot-apache/certbot_apache/_internal/entrypoint.py index d43094976..e31e1f4eb 100644 --- a/certbot-apache/certbot_apache/_internal/entrypoint.py +++ b/certbot-apache/certbot_apache/_internal/entrypoint.py @@ -1,7 +1,7 @@ """ Entry point for Apache Plugin """ # Pylint does not like disutils.version when running inside a venv. # See: https://github.com/PyCQA/pylint/issues/73 -from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error +from distutils.version import LooseVersion from certbot import util from certbot_apache._internal import configurator diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index c34abc2b4..5ea0ce8ec 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -1,8 +1,8 @@ """A class that performs HTTP-01 challenges for Apache""" import logging -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import errors from certbot.compat import filesystem from certbot.compat import os diff --git a/certbot-apache/certbot_apache/_internal/obj.py b/certbot-apache/certbot_apache/_internal/obj.py index 940bb6144..498766744 100644 --- a/certbot-apache/certbot_apache/_internal/obj.py +++ b/certbot-apache/certbot_apache/_internal/obj.py @@ -1,7 +1,7 @@ """Module contains classes used by the Apache Configurator.""" import re -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set from certbot.plugins import common diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py index a3ef2d760..2ab160c2f 100644 --- a/certbot-apache/certbot_apache/_internal/override_centos.py +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -4,7 +4,7 @@ import logging import pkg_resources import zope.interface -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import errors from certbot import interfaces from certbot import util diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py index aae3dc6e4..992672913 100644 --- a/certbot-apache/certbot_apache/_internal/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -7,9 +7,9 @@ import sys import six -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import errors from certbot.compat import os from certbot_apache._internal import apache_util @@ -321,7 +321,7 @@ class ApacheParser(object): for mod in matches: self.add_mod(mod.strip()) - def filter_args_num(self, matches, args): # pylint: disable=no-self-use + def filter_args_num(self, matches, args): """Filter out directives with specific number of arguments. This function makes the assumption that all related arguments are given @@ -715,7 +715,7 @@ class ApacheParser(object): return get_aug_path(arg) - def fnmatch_to_re(self, clean_fn_match): # pylint: disable=no-self-use + def fnmatch_to_re(self, clean_fn_match): """Method converts Apache's basic fnmatch to regular expression. Assumption - Configs are assumed to be well-formed and only writable by 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 3011b9823..7cb4e9722 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -5,7 +5,7 @@ import subprocess import zope.interface -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set from certbot._internal import configuration from certbot_compatibility_test import errors from certbot_compatibility_test import interfaces diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 2c3f880e0..d719f583b 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -15,8 +15,8 @@ from urllib3.util import connection from acme import challenges from acme import crypto_util from acme import messages -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Tuple from certbot import achallenges from certbot import errors as le_errors from certbot.tests import acme_util diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator.py b/certbot-compatibility-test/certbot_compatibility_test/validator.py index 796ebbe9d..b527ce16b 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator.py @@ -4,7 +4,7 @@ import socket import requests import six -from six.moves import xrange # pylint: disable=import-error, redefined-builtin +from six.moves import xrange from acme import crypto_util from acme import errors as acme_errors @@ -13,7 +13,6 @@ logger = logging.getLogger(__name__) class Validator(object): - # pylint: disable=no-self-use """Collection of functions to test a live webserver's configuration""" def certificate(self, cert, name, alt_host=None, port=443): 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 22124ac04..22dbcfa1f 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py @@ -38,7 +38,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add) add('credentials', help='Cloudflare credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Cloudflare API.' 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 2a0f12ea7..654c04c70 100644 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='CloudXNS credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the CloudXNS API.' 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 7f3abbe31..75e25a848 100644 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py @@ -30,7 +30,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add) add('credentials', help='DigitalOcean credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the DigitalOcean API.' 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 8c48d31e7..9f7f100d7 100644 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='DNSimple credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the DNSimple API.' 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 ed3146dce..4a1fcffc3 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py @@ -35,7 +35,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) add('credentials', help='DNS Made Easy credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the DNS Made Easy API.' 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 76c0ed584..39deddae5 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='Gehirn Infrastructure Service credentials file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Gehirn Infrastructure Service API.' 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 3aa910b52..4eaed1783 100644 --- a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py +++ b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py @@ -45,7 +45,7 @@ class Authenticator(dns_common.DNSAuthenticator): 'required permissions.)').format(ACCT_URL, PERMISSIONS_URL), default=None) - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Google Cloud DNS API.' @@ -148,7 +148,7 @@ class _GoogleClient(object): }, ] - changes = self.dns.changes() # changes | pylint: disable=no-member + changes = self.dns.changes() try: request = changes.create(project=self.project_id, managedZone=zone_id, body=data) @@ -213,7 +213,7 @@ class _GoogleClient(object): }, ] - changes = self.dns.changes() # changes | pylint: disable=no-member + changes = self.dns.changes() try: request = changes.create(project=self.project_id, managedZone=zone_id, body=data) @@ -264,7 +264,7 @@ class _GoogleClient(object): zone_dns_name_guesses = dns_common.base_domain_name_guesses(domain) - mz = self.dns.managedZones() # managedZones | pylint: disable=no-member + mz = self.dns.managedZones() for zone_name in zone_dns_name_guesses: try: request = mz.list(project=self.project_id, dnsName=zone_name + '.') 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 ea6046849..f7b3ec3d4 100644 --- a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py +++ b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py @@ -35,7 +35,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=1200) add('credentials', help='Linode credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Linode API.' 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 7c18c7131..d5b499c72 100644 --- a/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py +++ b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='LuaDNS credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the LuaDNS API.' 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 f5af37389..d328d80ce 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py +++ b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='NS1 credentials file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the NS1 API.' 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 a495983f2..11ae6b8f0 100644 --- a/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py +++ b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='OVH credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the OVH API.' 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 cb4d5addb..3bb4f444b 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -50,7 +50,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) add('credentials', help='RFC 2136 credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'RFC 2136 Dynamic Updates.' 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 637558304..6250d2274 100644 --- a/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py +++ b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py @@ -8,9 +8,9 @@ from botocore.exceptions import ClientError from botocore.exceptions import NoCredentialsError import zope.interface -from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import DefaultDict +from acme.magic_typing import Dict +from acme.magic_typing import List from certbot import errors from certbot import interfaces from certbot.plugins import dns_common @@ -41,13 +41,13 @@ class Authenticator(dns_common.DNSAuthenticator): self.r53 = boto3.client("route53") self._resource_records = collections.defaultdict(list) # type: DefaultDict[str, List[Dict[str, str]]] - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return "Solve a DNS01 challenge using AWS Route53" def _setup_credentials(self): pass - def _perform(self, domain, validation_name, validation): # pylint: disable=missing-docstring + def _perform(self, domain, validation_name, validation): pass def perform(self, achalls): 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 25042bfc6..67cfb2e97 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py @@ -35,7 +35,7 @@ class Authenticator(dns_common.DNSAuthenticator): add, default_propagation_seconds=90) add('credentials', help='Sakura Cloud credentials file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Sakura Cloud API.' diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index 52d8a08bc..459950aa1 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1,6 +1,6 @@ """Nginx Configuration""" # https://github.com/PyCQA/pylint/issues/73 -from distutils.version import LooseVersion # pylint: disable=no-name-in-module, import-error +from distutils.version import LooseVersion import logging import re import socket @@ -14,9 +14,9 @@ import zope.interface from acme import challenges from acme import crypto_util as acme_crypto_util -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import crypto_util from certbot import errors from certbot import interfaces @@ -696,7 +696,7 @@ class NginxConfigurator(common.Installer): ################################## # enhancement methods (IInstaller) ################################## - def supported_enhancements(self): # pylint: disable=no-self-use + def supported_enhancements(self): """Returns currently supported enhancements.""" return ['redirect', 'ensure-http-header', 'staple-ocsp'] @@ -915,7 +915,7 @@ class NginxConfigurator(common.Installer): """ nginx_restart(self.conf('ctl'), self.nginx_conf) - def config_test(self): # pylint: disable=no-self-use + def config_test(self): """Check the configuration of Nginx for errors. :raises .errors.MisconfigurationError: If config_test fails @@ -1090,7 +1090,7 @@ class NginxConfigurator(common.Installer): ########################################################################### # Challenges Section for IAuthenticator ########################################################################### - def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use + def get_chall_pref(self, unused_domain): """Return list of challenge preferences.""" return [challenges.HTTP01] diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py index 97b111576..2f6458f87 100644 --- a/certbot-nginx/certbot_nginx/_internal/http_01.py +++ b/certbot-nginx/certbot_nginx/_internal/http_01.py @@ -3,7 +3,7 @@ import logging from acme import challenges -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import errors from certbot.compat import os from certbot.plugins import common diff --git a/certbot-nginx/certbot_nginx/_internal/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py index 4fa1362a0..a8ac90427 100644 --- a/certbot-nginx/certbot_nginx/_internal/nginxparser.py +++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py @@ -20,7 +20,6 @@ import six logger = logging.getLogger(__name__) class RawNginxParser(object): - # pylint: disable=expression-not-assigned # pylint: disable=pointless-statement """A class that parses nginx configuration with pyparsing.""" diff --git a/certbot-nginx/certbot_nginx/_internal/parser.py b/certbot-nginx/certbot_nginx/_internal/parser.py index edb77a1c1..f71d7c018 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser.py +++ b/certbot-nginx/certbot_nginx/_internal/parser.py @@ -8,11 +8,11 @@ import re import pyparsing import six -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set +from acme.magic_typing import Tuple +from acme.magic_typing import Union from certbot import errors from certbot.compat import os from certbot_nginx._internal import nginxparser @@ -127,7 +127,6 @@ class NginxParser(object): return servers def get_vhosts(self): - # pylint: disable=cell-var-from-loop """Gets list of all 'virtual hosts' found in Nginx configuration. Technically this is a misnomer because Nginx does not have virtual hosts, it has 'server blocks'. diff --git a/certbot-nginx/certbot_nginx/_internal/parser_obj.py b/certbot-nginx/certbot_nginx/_internal/parser_obj.py index e03913887..61b31b2d5 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser_obj.py +++ b/certbot-nginx/certbot_nginx/_internal/parser_obj.py @@ -6,7 +6,7 @@ import logging import six -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import errors logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/auth_handler.py b/certbot/certbot/_internal/auth_handler.py index e4ad91247..7ea2a1de8 100644 --- a/certbot/certbot/_internal/auth_handler.py +++ b/certbot/certbot/_internal/auth_handler.py @@ -8,9 +8,9 @@ import zope.component from acme import challenges from acme import errors as acme_errors from acme import messages -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Tuple from certbot import achallenges from certbot import errors from certbot import interfaces diff --git a/certbot/certbot/_internal/cert_manager.py b/certbot/certbot/_internal/cert_manager.py index 298e7d269..e6cbd5c2c 100644 --- a/certbot/certbot/_internal/cert_manager.py +++ b/certbot/certbot/_internal/cert_manager.py @@ -7,7 +7,7 @@ import traceback import pytz import zope.component -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import crypto_util from certbot import errors from certbot import interfaces diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py index d5d498b4d..c0dbf7424 100644 --- a/certbot/certbot/_internal/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -15,9 +15,9 @@ import zope.interface from zope.interface import interfaces as zope_interfaces from acme import challenges -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Dict +from acme.magic_typing import Optional import certbot from certbot import crypto_util from certbot import errors @@ -505,7 +505,7 @@ class HelpfulArgumentParser(object): " and ".join(flag_default("config_files")))) # This is the only way to turn off overly verbose config flag documentation - self.parser._add_config_file_help = False # pylint: disable=protected-access + self.parser._add_config_file_help = False # Help that are synonyms for --help subcommands COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py index 9ce741e38..526f4200f 100644 --- a/certbot/certbot/_internal/client.py +++ b/certbot/certbot/_internal/client.py @@ -14,8 +14,8 @@ from acme import client as acme_client from acme import crypto_util as acme_crypto_util from acme import errors as acme_errors from acme import messages -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Optional import certbot from certbot import crypto_util from certbot import errors @@ -343,7 +343,7 @@ class Client(object): orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names) authzr = orderr.authorizations - auth_domains = set(a.body.identifier.value for a in authzr) # pylint: disable=not-an-iterable + auth_domains = set(a.body.identifier.value for a in authzr) successful_domains = [d for d in domains if d in auth_domains] # allow_subset_of_names is currently disabled for wildcard diff --git a/certbot/certbot/_internal/configuration.py b/certbot/certbot/_internal/configuration.py index f3db207db..f1e85f9fe 100644 --- a/certbot/certbot/_internal/configuration.py +++ b/certbot/certbot/_internal/configuration.py @@ -65,7 +65,7 @@ class NamespaceConfig(object): return (parsed.netloc + parsed.path).replace('/', os.path.sep) @property - def accounts_dir(self): # pylint: disable=missing-docstring + def accounts_dir(self): # pylint: disable=missing-function-docstring return self.accounts_dir_for_server_path(self.server_path) def accounts_dir_for_server_path(self, server_path): @@ -75,23 +75,23 @@ class NamespaceConfig(object): self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path) @property - def backup_dir(self): # pylint: disable=missing-docstring + def backup_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) @property - def csr_dir(self): # pylint: disable=missing-docstring + def csr_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.config_dir, constants.CSR_DIR) @property - def in_progress_dir(self): # pylint: disable=missing-docstring + def in_progress_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) @property - def key_dir(self): # pylint: disable=missing-docstring + def key_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.config_dir, constants.KEY_DIR) @property - def temp_checkpoint_dir(self): # pylint: disable=missing-docstring + def temp_checkpoint_dir(self): # pylint: disable=missing-function-docstring return os.path.join( self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) @@ -102,15 +102,15 @@ class NamespaceConfig(object): return type(self)(new_ns) @property - def default_archive_dir(self): # pylint: disable=missing-docstring + def default_archive_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR) @property - def live_dir(self): # pylint: disable=missing-docstring + def live_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.config_dir, constants.LIVE_DIR) @property - def renewal_configs_dir(self): # pylint: disable=missing-docstring + def renewal_configs_dir(self): # pylint: disable=missing-function-docstring return os.path.join( self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR) diff --git a/certbot/certbot/_internal/error_handler.py b/certbot/certbot/_internal/error_handler.py index 5ca3cc57e..41c12eafa 100644 --- a/certbot/certbot/_internal/error_handler.py +++ b/certbot/certbot/_internal/error_handler.py @@ -4,11 +4,11 @@ import logging import signal import traceback -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Callable +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Union from certbot import errors from certbot.compat import os diff --git a/certbot/certbot/_internal/hooks.py b/certbot/certbot/_internal/hooks.py index 25addd915..589c59e89 100644 --- a/certbot/certbot/_internal/hooks.py +++ b/certbot/certbot/_internal/hooks.py @@ -5,8 +5,8 @@ import logging from subprocess import PIPE from subprocess import Popen -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import errors from certbot import util from certbot.compat import filesystem diff --git a/certbot/certbot/_internal/lock.py b/certbot/certbot/_internal/lock.py index 7823eaac3..8f3c28006 100644 --- a/certbot/certbot/_internal/lock.py +++ b/certbot/certbot/_internal/lock.py @@ -2,15 +2,15 @@ import errno import logging -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional from certbot import errors from certbot.compat import filesystem from certbot.compat import os try: - import fcntl # pylint: disable=import-error + import fcntl except ImportError: - import msvcrt # pylint: disable=import-error + import msvcrt POSIX_MODE = False else: POSIX_MODE = True @@ -115,10 +115,10 @@ class _BaseLockMechanism(object): """ return self._fd is not None - def acquire(self): # pylint: disable=missing-docstring + def acquire(self): # pylint: disable=missing-function-docstring pass # pragma: no cover - def release(self): # pylint: disable=missing-docstring + def release(self): # pylint: disable=missing-function-docstring pass # pragma: no cover diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 72fcfca71..8674cd151 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -11,7 +11,7 @@ import josepy as jose import zope.component from acme import errors as acme_errors -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union import certbot from certbot import crypto_util from certbot import errors diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index d7d6390f7..d98a4cb0c 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -8,7 +8,7 @@ import six import zope.interface import zope.interface.verify -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict from certbot import errors from certbot import interfaces from certbot._internal import constants @@ -195,14 +195,12 @@ class PluginsRegistry(Mapping): # Pylint checks for super init, but also claims the super # has no __init__member - # pylint: disable=super-init-not-called self._plugins = collections.OrderedDict(sorted(six.iteritems(plugins))) @classmethod def find_all(cls): """Find plugins using setuptools entry points.""" plugins = {} # type: Dict[str, PluginEntryPoint] - # pylint: disable=not-callable entry_points = itertools.chain( pkg_resources.iter_entry_points( constants.SETUPTOOLS_PLUGINS_ENTRY_POINT), @@ -212,7 +210,6 @@ class PluginsRegistry(Mapping): plugin_ep = PluginEntryPoint(entry_point) assert plugin_ep.name not in plugins, ( "PREFIX_FREE_DISTRIBUTIONS messed up") - # providedBy | pylint: disable=no-member 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 be6abaad4..3204fe1da 100644 --- a/certbot/certbot/_internal/plugins/manual.py +++ b/certbot/certbot/_internal/plugins/manual.py @@ -3,7 +3,7 @@ import zope.component import zope.interface from acme import challenges -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict from certbot import achallenges # pylint: disable=unused-import from certbot import errors from certbot import interfaces @@ -81,7 +81,7 @@ permitted by DNS standards.) add('public-ip-logging-ok', action='store_true', help='Automatically allows public IP logging (default: Ask)') - def prepare(self): # pylint: disable=missing-docstring + def prepare(self): # pylint: disable=missing-function-docstring if self.config.noninteractive_mode and not self.conf('auth-hook'): raise errors.PluginError( 'An authentication script must be provided with --{0} when ' @@ -97,17 +97,17 @@ permitted by DNS standards.) hook_prefix = self.option_name(name)[:-len('-hook')] hooks.validate_hook(hook, hook_prefix) - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return ( 'This plugin allows the user to customize setup for domain ' 'validation challenges either through shell scripts provided by ' 'the user or by performing the setup manually.') def get_chall_pref(self, domain): - # pylint: disable=missing-docstring,no-self-use,unused-argument + # pylint: disable=unused-argument,missing-function-docstring return [challenges.HTTP01, challenges.DNS01] - def perform(self, achalls): # pylint: disable=missing-docstring + def perform(self, achalls): # pylint: disable=missing-function-docstring self._verify_ip_logging_ok() if self.conf('auth-hook'): perform_achall = self._perform_achall_with_script @@ -170,7 +170,7 @@ permitted by DNS standards.) display.notification(msg, wrap=False, force_interactive=True) self.subsequent_any_challenge = True - def cleanup(self, achalls): # pylint: disable=missing-docstring + def cleanup(self, achalls): # pylint: disable=missing-function-docstring if self.conf('cleanup-hook'): for achall in achalls: env = self.env.pop(achall) diff --git a/certbot/certbot/_internal/plugins/null.py b/certbot/certbot/_internal/plugins/null.py index bf4615497..cf7c05a2b 100644 --- a/certbot/certbot/_internal/plugins/null.py +++ b/certbot/certbot/_internal/plugins/null.py @@ -18,7 +18,7 @@ class Installer(common.Plugin): description = "Null Installer" hidden = True - # pylint: disable=missing-docstring,no-self-use + # pylint: disable=missing-function-docstring def prepare(self): pass # pragma: no cover diff --git a/certbot/certbot/_internal/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py index 80421299e..bbb56178c 100644 --- a/certbot/certbot/_internal/plugins/standalone.py +++ b/certbot/certbot/_internal/plugins/standalone.py @@ -11,12 +11,12 @@ import zope.interface from acme import challenges from acme import standalone as acme_standalone -from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import TYPE_CHECKING # pylint: disable=unused-import, no-name-in-module -from certbot import achallenges # pylint: disable=unused-import +from acme.magic_typing import DefaultDict +from acme.magic_typing import Dict +from acme.magic_typing import Set +from acme.magic_typing import Tuple +from acme.magic_typing import TYPE_CHECKING +from certbot import achallenges from certbot import errors from certbot import interfaces from certbot.plugins import common @@ -139,20 +139,20 @@ class Authenticator(common.Plugin): def add_parser_arguments(cls, add): pass # No additional argument for the standalone plugin parser - def more_info(self): # pylint: disable=missing-docstring + def more_info(self): # pylint: disable=missing-function-docstring return("This authenticator creates its own ephemeral TCP listener " "on the necessary port in order to respond to incoming " "http-01 challenges from the certificate authority. Therefore, " "it does not rely on any existing server program.") - def prepare(self): # pylint: disable=missing-docstring + def prepare(self): # pylint: disable=missing-function-docstring pass def get_chall_pref(self, domain): - # pylint: disable=unused-argument,missing-docstring + # pylint: disable=unused-argument,missing-function-docstring return [challenges.HTTP01] - def perform(self, achalls): # pylint: disable=missing-docstring + def perform(self, achalls): # pylint: disable=missing-function-docstring return [self._try_perform_single(achall) for achall in achalls] def _try_perform_single(self, achall): @@ -177,7 +177,7 @@ class Authenticator(common.Plugin): self.http_01_resources.add(resource) return servers, response - def cleanup(self, achalls): # pylint: disable=missing-docstring + def cleanup(self, achalls): # pylint: disable=missing-function-docstring # reduce self.served and close servers if no challenges are served for unused_servers, server_achalls in self.served.items(): for achall in achalls: diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py index c7737e0d1..042b60656 100644 --- a/certbot/certbot/_internal/plugins/webroot.py +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -9,11 +9,11 @@ import six import zope.component import zope.interface -from acme import challenges # pylint: disable=unused-import -from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme import challenges +from acme.magic_typing import DefaultDict +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import achallenges # pylint: disable=unused-import from certbot import errors from certbot import interfaces @@ -42,7 +42,7 @@ necessary validation resources to appropriate paths on the file system. It expects that there is some other HTTP server configured to serve all files under specified web root ({0}).""" - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return self.MORE_INFO.format(self.conf("path")) @classmethod @@ -64,7 +64,7 @@ to serve all files under specified web root ({0}).""" '{"example.com":"/var/www"}.') def get_chall_pref(self, domain): # pragma: no cover - # pylint: disable=missing-docstring,no-self-use,unused-argument + # pylint: disable=unused-argument,missing-function-docstring return [challenges.HTTP01] def __init__(self, *args, **kwargs): @@ -75,10 +75,10 @@ to serve all files under specified web root ({0}).""" # stack of dirs successfully created by this authenticator self._created_dirs = [] # type: List[str] - def prepare(self): # pylint: disable=missing-docstring + def prepare(self): # pylint: disable=missing-function-docstring pass - def perform(self, achalls): # pylint: disable=missing-docstring + def perform(self, achalls): # pylint: disable=missing-function-docstring self._set_webroots(achalls) self._create_challenge_dirs() @@ -213,7 +213,7 @@ to serve all files under specified web root ({0}).""" self.performed[root_path].add(achall) return response - def cleanup(self, achalls): # pylint: disable=missing-docstring + def cleanup(self, achalls): # pylint: disable=missing-function-docstring for achall in achalls: root_path = self.full_roots.get(achall.domain, None) if root_path is not None: diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index bf30404f5..fd23b0d18 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -13,7 +13,7 @@ import OpenSSL import six import zope.component -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import crypto_util from certbot import errors from certbot import interfaces diff --git a/certbot/certbot/_internal/reporter.py b/certbot/certbot/_internal/reporter.py index 947f343d4..3b959f4e1 100644 --- a/certbot/certbot/_internal/reporter.py +++ b/certbot/certbot/_internal/reporter.py @@ -6,7 +6,7 @@ import logging import sys import textwrap -from six.moves import queue # type: ignore # pylint: disable=import-error +from six.moves import queue # type: ignore import zope.interface from certbot import interfaces diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index 964515eee..6a34355a8 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -883,7 +883,7 @@ class RenewableCert(interfaces.RenewableCert): return crypto_util.get_names_from_cert(f.read()) def ocsp_revoked(self, version=None): - # pylint: disable=no-self-use,unused-argument + # pylint: disable=unused-argument """Is the specified cert version revoked according to OCSP? Also returns True if the cert version is declared as intended diff --git a/certbot/certbot/compat/_path.py b/certbot/certbot/compat/_path.py index 5c5fe460e..44c5e300f 100644 --- a/certbot/certbot/compat/_path.py +++ b/certbot/certbot/compat/_path.py @@ -9,7 +9,7 @@ from __future__ import absolute_import # First round of wrapping: we import statically all public attributes exposed by the os.path # module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path # members are available in certbot.compat.path. -from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden +from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,os-module-forbidden # Second round of wrapping: we import dynamically all attributes from the os.path module that have # not yet been imported by the first round (static star import). diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index 65bb53f38..88c2916fa 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -5,12 +5,11 @@ import errno import os # pylint: disable=os-module-forbidden import stat -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Tuple # pylint: disable=unused-import +from acme.magic_typing import Union # pylint: disable=unused-import try: - # pylint: disable=import-error import ntsecuritycon import win32security import win32con diff --git a/certbot/certbot/compat/misc.py b/certbot/certbot/compat/misc.py index ffe611edb..956c56370 100644 --- a/certbot/certbot/compat/misc.py +++ b/certbot/certbot/compat/misc.py @@ -11,7 +11,7 @@ from certbot import errors from certbot.compat import os try: - from win32com.shell import shell as shellwin32 # pylint: disable=import-error + from win32com.shell import shell as shellwin32 POSIX_MODE = False except ImportError: # pragma: no cover POSIX_MODE = True diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index 9aae75991..9136445bc 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -23,7 +23,7 @@ import six import zope.component from acme import crypto_util as acme_crypto_util -from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import IO # pylint: disable=unused-import from certbot import errors from certbot import interfaces from certbot import util diff --git a/certbot/certbot/display/util.py b/certbot/certbot/display/util.py index ba2dd4ecf..05330b1a9 100644 --- a/certbot/certbot/display/util.py +++ b/certbot/certbot/display/util.py @@ -334,7 +334,6 @@ class FileDisplay(object): return self.input(message, default, cli_flag, force_interactive) def _scrub_checklist_input(self, indices, tags): - # pylint: disable=no-self-use """Validate input and transform indices to appropriate tags. :param list indices: input diff --git a/certbot/certbot/interfaces.py b/certbot/certbot/interfaces.py index e96712d23..81b41d1ec 100644 --- a/certbot/certbot/interfaces.py +++ b/certbot/certbot/interfaces.py @@ -4,7 +4,7 @@ import abc import six import zope.interface -# pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class +# pylint: disable=no-self-argument,no-method-argument,inherit-non-class @six.add_metaclass(abc.ABCMeta) diff --git a/certbot/certbot/ocsp.py b/certbot/certbot/ocsp.py index 5c3cfdd59..6a95f26fa 100644 --- a/certbot/certbot/ocsp.py +++ b/certbot/certbot/ocsp.py @@ -16,8 +16,8 @@ from cryptography.hazmat.primitives import serialization import pytz import requests -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional +from acme.magic_typing import Tuple from certbot import crypto_util from certbot import errors from certbot import util @@ -26,7 +26,7 @@ from certbot.interfaces import RenewableCert # pylint: disable=unused-import try: # Only cryptography>=2.5 has ocsp module # and signature_hash_algorithm attribute in OCSPResponse class - from cryptography.x509 import ocsp # pylint: disable=import-error, ungrouped-imports + from cryptography.x509 import ocsp # pylint: disable=ungrouped-imports getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') except (ImportError, AttributeError): # pragma: no cover ocsp = None # type: ignore diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py index 6fa1e76f8..7d21e6d0a 100644 --- a/certbot/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -10,7 +10,7 @@ from josepy import util as jose_util import pkg_resources import zope.interface -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import achallenges # pylint: disable=unused-import from certbot import crypto_util from certbot import errors @@ -74,7 +74,6 @@ class Plugin(object): """ # dummy function, doesn't check if dest.startswith(self.dest_namespace) def add(arg_name_no_prefix, *args, **kwargs): - # pylint: disable=missing-docstring return parser.add_argument( "--{0}{1}".format(option_namespace(name), arg_name_no_prefix), *args, **kwargs) diff --git a/certbot/certbot/plugins/dns_common.py b/certbot/certbot/plugins/dns_common.py index d31266434..245b7dc05 100644 --- a/certbot/certbot/plugins/dns_common.py +++ b/certbot/certbot/plugins/dns_common.py @@ -37,13 +37,13 @@ class DNSAuthenticator(common.Plugin): help='The number of seconds to wait for DNS to propagate before asking the ACME server ' 'to verify the DNS record.') - def get_chall_pref(self, unused_domain): # pylint: disable=missing-docstring,no-self-use + def get_chall_pref(self, unused_domain): # pylint: disable=missing-function-docstring return [challenges.DNS01] - def prepare(self): # pylint: disable=missing-docstring + def prepare(self): # pylint: disable=missing-function-docstring pass - def perform(self, achalls): # pylint: disable=missing-docstring + def perform(self, achalls): # pylint: disable=missing-function-docstring self._setup_credentials() self._attempt_cleanup = True @@ -66,7 +66,7 @@ class DNSAuthenticator(common.Plugin): return responses - def cleanup(self, achalls): # pylint: disable=missing-docstring + def cleanup(self, achalls): # pylint: disable=missing-function-docstring if self._attempt_cleanup: for achall in achalls: domain = achall.domain diff --git a/certbot/certbot/plugins/dns_common_lexicon.py b/certbot/certbot/plugins/dns_common_lexicon.py index 3e28a291b..c3d80ca29 100644 --- a/certbot/certbot/plugins/dns_common_lexicon.py +++ b/certbot/certbot/plugins/dns_common_lexicon.py @@ -4,9 +4,9 @@ import logging from requests.exceptions import HTTPError from requests.exceptions import RequestException -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Dict +from acme.magic_typing import Union from certbot import errors from certbot.plugins import dns_common diff --git a/certbot/certbot/plugins/enhancements.py b/certbot/certbot/plugins/enhancements.py index f8d9db7dc..be9b7933d 100644 --- a/certbot/certbot/plugins/enhancements.py +++ b/certbot/certbot/plugins/enhancements.py @@ -3,9 +3,9 @@ import abc import six -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Dict +from acme.magic_typing import List from certbot._internal import constants ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] diff --git a/certbot/certbot/plugins/storage.py b/certbot/certbot/plugins/storage.py index 7956295d2..fe3a97dd5 100644 --- a/certbot/certbot/plugins/storage.py +++ b/certbot/certbot/plugins/storage.py @@ -2,8 +2,8 @@ import json import logging -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Dict from certbot import errors from certbot.compat import filesystem from certbot.compat import os diff --git a/certbot/certbot/reverter.py b/certbot/certbot/reverter.py index 47a77c80a..80ba0f599 100644 --- a/certbot/certbot/reverter.py +++ b/certbot/certbot/reverter.py @@ -195,7 +195,7 @@ class Reverter(object): with open(os.path.join(cp_dir, "CHANGES_SINCE"), "a") as notes_fd: notes_fd.write(save_notes) - def _read_and_append(self, filepath): # pylint: disable=no-self-use + def _read_and_append(self, filepath): """Reads the file lines and returns a file obj. Read the file returning the lines, and a pointer to the end of the file. @@ -250,7 +250,7 @@ class Reverter(object): raise errors.ReverterError( "Unable to remove directory: %s" % cp_dir) - def _run_undo_commands(self, filepath): # pylint: disable=no-self-use + def _run_undo_commands(self, filepath): """Run all commands in a file.""" # NOTE: csv module uses native strings. That is, bytes on Python 2 and # unicode on Python 3 @@ -413,7 +413,7 @@ class Reverter(object): "Incomplete or failed recovery for IN_PROGRESS checkpoint " "- %s" % self.config.in_progress_dir) - def _remove_contained_files(self, file_list): # pylint: disable=no-self-use + def _remove_contained_files(self, file_list): """Erase all files contained within file_list. :param str file_list: file containing list of file paths to be deleted diff --git a/certbot/certbot/tests/acme_util.py b/certbot/certbot/tests/acme_util.py index 3d560dcbc..f4a20ea86 100644 --- a/certbot/certbot/tests/acme_util.py +++ b/certbot/certbot/tests/acme_util.py @@ -27,7 +27,7 @@ def gen_combos(challbs): return tuple((i,) for i, _ in enumerate(challbs)) -def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name +def chall_to_challb(chall, status): """Return ChallengeBody from Challenge.""" kwargs = { "chall": chall, @@ -67,7 +67,6 @@ def gen_authzr(authz_status, domain, challs, statuses, combos=True): :param bool combos: Whether or not to add combinations """ - # pylint: disable=redefined-outer-name challbs = tuple( chall_to_challb(chall, status) for chall, status in six.moves.zip(challs, statuses) diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py index a9870b0fd..8b28b1080 100644 --- a/certbot/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -14,7 +14,7 @@ import mock import OpenSSL import pkg_resources import six -from six.moves import reload_module # pylint: disable=import-error +from six.moves import reload_module from certbot import interfaces from certbot import util diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py index 0a47cd87a..aff2952f7 100644 --- a/certbot/certbot/util.py +++ b/certbot/certbot/util.py @@ -5,7 +5,7 @@ import argparse import atexit import collections from collections import OrderedDict -import distutils.version # pylint: disable=import-error,no-name-in-module +import distutils.version import errno import logging import platform @@ -17,8 +17,8 @@ import sys import configargparse import six -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple +from acme.magic_typing import Union from certbot import errors from certbot._internal import constants from certbot._internal import lock @@ -26,7 +26,7 @@ from certbot.compat import filesystem from certbot.compat import os if sys.platform.startswith('linux'): - import distro # pylint: disable=import-error + import distro _USE_DISTRO = True else: _USE_DISTRO = False diff --git a/letshelp-certbot/letshelp_certbot/apache.py b/letshelp-certbot/letshelp_certbot/apache.py index ebe4e3671..a5947399a 100755 --- a/letshelp-certbot/letshelp_certbot/apache.py +++ b/letshelp-certbot/letshelp_certbot/apache.py @@ -16,7 +16,7 @@ import textwrap import six -from letshelp_certbot.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from letshelp_certbot.magic_typing import List _DESCRIPTION = """ Let's Help is a simple script you can run to help out the Certbot diff --git a/letshelp-certbot/letshelp_certbot/magic_typing.py b/letshelp-certbot/letshelp_certbot/magic_typing.py index 5a6358c69..d6b1ff056 100644 --- a/letshelp-certbot/letshelp_certbot/magic_typing.py +++ b/letshelp-certbot/letshelp_certbot/magic_typing.py @@ -10,7 +10,6 @@ class TypingClass(object): try: # mypy doesn't respect modifying sys.modules from typing import * # pylint: disable=wildcard-import, unused-wildcard-import - # pylint: disable=unused-import from typing import Collection, IO # type: ignore # pylint: enable=unused-import except ImportError: diff --git a/letshelp-certbot/letshelp_certbot/magic_typing_test.py b/letshelp-certbot/letshelp_certbot/magic_typing_test.py index 200ca03b8..41bc245fa 100644 --- a/letshelp-certbot/letshelp_certbot/magic_typing_test.py +++ b/letshelp-certbot/letshelp_certbot/magic_typing_test.py @@ -18,7 +18,7 @@ class MagicTypingTest(unittest.TestCase): sys.modules['typing'] = typing_class_mock if 'letshelp_certbot.magic_typing' in sys.modules: del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover - from letshelp_certbot.magic_typing import Text # pylint: disable=no-name-in-module + from letshelp_certbot.magic_typing import Text self.assertEqual(Text, text_mock) del sys.modules['letshelp_certbot.magic_typing'] sys.modules['typing'] = temp_typing @@ -31,7 +31,7 @@ class MagicTypingTest(unittest.TestCase): sys.modules['typing'] = None if 'letshelp_certbot.magic_typing' in sys.modules: del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover - from letshelp_certbot.magic_typing import Text # pylint: disable=no-name-in-module + from letshelp_certbot.magic_typing import Text self.assertTrue(Text is None) del sys.modules['letshelp_certbot.magic_typing'] sys.modules['typing'] = temp_typing From 3f52695ec27a4b5fdfe675982e959f7fffbccb18 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 14 Feb 2020 17:18:53 -0800 Subject: [PATCH 2/8] more robustly stop patches (#7763) --- certbot/tests/cert_manager_test.py | 8 +++--- certbot/tests/main_test.py | 40 +++++++++++++----------------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 81134f02f..eb8005b2b 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -564,14 +564,12 @@ class GetCertnameTest(unittest.TestCase): """Tests for certbot._internal.cert_manager.""" def setUp(self): - self.get_utility_patch = test_util.patch_get_utility() - self.mock_get_utility = self.get_utility_patch.start() + get_utility_patch = test_util.patch_get_utility() + self.mock_get_utility = get_utility_patch.start() + self.addCleanup(get_utility_patch.stop) self.config = mock.MagicMock() self.config.certname = None - def tearDown(self): - self.get_utility_patch.stop() - @mock.patch('certbot._internal.storage.renewal_conf_files') @mock.patch('certbot._internal.storage.lineagename_for_filename') def test_get_certnames(self, mock_name, mock_files): diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 7b22c81d6..7ebe5e66a 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -62,7 +62,7 @@ class RunTest(test_util.ConfigTestCase): def setUp(self): super(RunTest, self).setUp() self.domain = 'example.org' - self.patches = [ + patches = [ mock.patch('certbot._internal.main._get_and_save_cert'), mock.patch('certbot._internal.main.display_ops.success_installation'), mock.patch('certbot._internal.main.display_ops.success_renewal'), @@ -71,17 +71,15 @@ class RunTest(test_util.ConfigTestCase): mock.patch('certbot._internal.main._report_new_cert'), mock.patch('certbot._internal.main._find_cert')] - self.mock_auth = self.patches[0].start() - self.mock_success_installation = self.patches[1].start() - self.mock_success_renewal = self.patches[2].start() - self.mock_init = self.patches[3].start() - self.mock_suggest_donation = self.patches[4].start() - self.mock_report_cert = self.patches[5].start() - self.mock_find_cert = self.patches[6].start() - - def tearDown(self): - for patch in self.patches: - patch.stop() + self.mock_auth = patches[0].start() + self.mock_success_installation = patches[1].start() + self.mock_success_renewal = patches[2].start() + self.mock_init = patches[3].start() + self.mock_suggest_donation = patches[4].start() + self.mock_report_cert = patches[5].start() + self.mock_find_cert = patches[6].start() + for patch in patches: + self.addCleanup(patch.stop) def _call(self): args = '-a webroot -i null -d {0}'.format(self.domain).split() @@ -243,16 +241,18 @@ class RevokeTest(test_util.TempDirTestCase): with open(self.tmp_cert_path, 'r') as f: self.tmp_cert = (self.tmp_cert_path, f.read()) - self.patches = [ + patches = [ mock.patch('acme.client.BackwardsCompatibleClientV2'), mock.patch('certbot._internal.client.Client'), mock.patch('certbot._internal.main._determine_account'), mock.patch('certbot._internal.main.display_ops.success_revocation') ] - self.mock_acme_client = self.patches[0].start() - self.patches[1].start() - self.mock_determine_account = self.patches[2].start() - self.mock_success_revoke = self.patches[3].start() + self.mock_acme_client = patches[0].start() + patches[1].start() + self.mock_determine_account = patches[2].start() + self.mock_success_revoke = patches[3].start() + for patch in patches: + self.addCleanup(patch.stop) from certbot._internal.account import Account @@ -265,12 +265,6 @@ class RevokeTest(test_util.TempDirTestCase): self.mock_determine_account.return_value = (self.acc, None) - def tearDown(self): - super(RevokeTest, self).tearDown() - - for patch in self.patches: - patch.stop() - def _call(self, args=None): if not args: args = 'revoke --cert-path={0} ' From fd64c8c33b2176e6569d64d30776bd5fc9fd3820 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 14 Feb 2020 17:19:19 -0800 Subject: [PATCH 3/8] Remove letshelp-certbot (#7761) * remove references to letshelp * remove letshelp files * Remove line continuation Co-authored-by: ohemorange --- certbot/docs/packaging.rst | 2 +- letshelp-certbot/LICENSE.txt | 190 ----------- letshelp-certbot/MANIFEST.in | 4 - letshelp-certbot/README.rst | 1 - letshelp-certbot/docs/.gitignore | 1 - letshelp-certbot/docs/Makefile | 192 ----------- letshelp-certbot/docs/_static/.gitignore | 0 letshelp-certbot/docs/_templates/.gitignore | 0 letshelp-certbot/docs/api.rst | 8 - letshelp-certbot/docs/api/index.rst | 11 - letshelp-certbot/docs/conf.py | 310 ----------------- letshelp-certbot/docs/index.rst | 27 -- letshelp-certbot/docs/make.bat | 263 --------------- letshelp-certbot/letshelp_certbot/__init__.py | 1 - letshelp-certbot/letshelp_certbot/apache.py | 314 ------------------ .../letshelp_certbot/apache_test.py | 241 -------------- .../letshelp_certbot/magic_typing.py | 16 - .../letshelp_certbot/magic_typing_test.py | 41 --- .../testdata/mods-available/ssl.load | 2 - .../testdata/mods-enabled/ssl.load | 1 - .../testdata/super_secret_file.txt | 1 - .../testdata/uncommonly_named_k3y | 6 - .../testdata/uncommonly_named_p4sswd | 1 - .../readthedocs.org.requirements.txt | 10 - letshelp-certbot/setup.cfg | 2 - letshelp-certbot/setup.py | 58 ---- linter_plugin.py | 2 +- mypy.ini | 3 - tools/_venv_common.py | 1 - tools/install_and_test.py | 2 +- tox.cover.py | 5 +- tox.ini | 4 +- 32 files changed, 6 insertions(+), 1714 deletions(-) delete mode 100644 letshelp-certbot/LICENSE.txt delete mode 100644 letshelp-certbot/MANIFEST.in delete mode 100644 letshelp-certbot/README.rst delete mode 100644 letshelp-certbot/docs/.gitignore delete mode 100644 letshelp-certbot/docs/Makefile delete mode 100644 letshelp-certbot/docs/_static/.gitignore delete mode 100644 letshelp-certbot/docs/_templates/.gitignore delete mode 100644 letshelp-certbot/docs/api.rst delete mode 100644 letshelp-certbot/docs/api/index.rst delete mode 100644 letshelp-certbot/docs/conf.py delete mode 100644 letshelp-certbot/docs/index.rst delete mode 100644 letshelp-certbot/docs/make.bat delete mode 100644 letshelp-certbot/letshelp_certbot/__init__.py delete mode 100755 letshelp-certbot/letshelp_certbot/apache.py delete mode 100644 letshelp-certbot/letshelp_certbot/apache_test.py delete mode 100644 letshelp-certbot/letshelp_certbot/magic_typing.py delete mode 100644 letshelp-certbot/letshelp_certbot/magic_typing_test.py delete mode 100644 letshelp-certbot/letshelp_certbot/testdata/mods-available/ssl.load delete mode 120000 letshelp-certbot/letshelp_certbot/testdata/mods-enabled/ssl.load delete mode 100644 letshelp-certbot/letshelp_certbot/testdata/super_secret_file.txt delete mode 100644 letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_k3y delete mode 100644 letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_p4sswd delete mode 100644 letshelp-certbot/readthedocs.org.requirements.txt delete mode 100644 letshelp-certbot/setup.cfg delete mode 100644 letshelp-certbot/setup.py diff --git a/certbot/docs/packaging.rst b/certbot/docs/packaging.rst index 7b0b1d41a..d2a94dbe7 100644 --- a/certbot/docs/packaging.rst +++ b/certbot/docs/packaging.rst @@ -38,7 +38,7 @@ Notes for package maintainers 0. Please use our tagged releases, not ``master``! -1. Do not package ``certbot-compatibility-test`` or ``letshelp-certbot`` - it's only used internally. +1. Do not package ``certbot-compatibility-test`` as it's only used internally. 2. To run tests on our packages, you should use ``python setup.py test``. Doing things like running ``pytest`` directly on our package files may not work because Certbot relies on setuptools to register and find its plugins. diff --git a/letshelp-certbot/LICENSE.txt b/letshelp-certbot/LICENSE.txt deleted file mode 100644 index 981c46c9f..000000000 --- a/letshelp-certbot/LICENSE.txt +++ /dev/null @@ -1,190 +0,0 @@ - Copyright 2015 Electronic Frontier Foundation and others - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/letshelp-certbot/MANIFEST.in b/letshelp-certbot/MANIFEST.in deleted file mode 100644 index 623392f28..000000000 --- a/letshelp-certbot/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include LICENSE.txt -include README.rst -recursive-include docs * -recursive-include letshelp_certbot/testdata * diff --git a/letshelp-certbot/README.rst b/letshelp-certbot/README.rst deleted file mode 100644 index bbe2f2570..000000000 --- a/letshelp-certbot/README.rst +++ /dev/null @@ -1 +0,0 @@ -Let's help Certbot client diff --git a/letshelp-certbot/docs/.gitignore b/letshelp-certbot/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/letshelp-certbot/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/letshelp-certbot/docs/Makefile b/letshelp-certbot/docs/Makefile deleted file mode 100644 index 4b392ab8d..000000000 --- a/letshelp-certbot/docs/Makefile +++ /dev/null @@ -1,192 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/letshelp-certbot.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/letshelp-certbot.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/letshelp-certbot" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/letshelp-certbot" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/letshelp-certbot/docs/_static/.gitignore b/letshelp-certbot/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/letshelp-certbot/docs/_templates/.gitignore b/letshelp-certbot/docs/_templates/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/letshelp-certbot/docs/api.rst b/letshelp-certbot/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/letshelp-certbot/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/letshelp-certbot/docs/api/index.rst b/letshelp-certbot/docs/api/index.rst deleted file mode 100644 index 5ced5f501..000000000 --- a/letshelp-certbot/docs/api/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -:mod:`letshelp_certbot` ---------------------------- - -.. automodule:: letshelp_certbot - :members: - -:mod:`letshelp_certbot.apache` -================================== - -.. automodule:: letshelp_certbot.apache - :members: diff --git a/letshelp-certbot/docs/conf.py b/letshelp-certbot/docs/conf.py deleted file mode 100644 index b4289a345..000000000 --- a/letshelp-certbot/docs/conf.py +++ /dev/null @@ -1,310 +0,0 @@ -# -*- coding: utf-8 -*- -# -# letshelp-certbot documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 18 13:40:19 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import os -import shlex -import sys - -here = os.path.abspath(os.path.dirname(__file__)) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', -] - -autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'letshelp-certbot' -copyright = u'2014-2015, Let\'s Encrypt Project' -author = u'Certbot Project' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0' -# The full version, including alpha/beta/rc tags. -release = '0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'en' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -default_role = 'py:obj' - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'letshelp-certbotdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - #'preamble': '', - - # Latex figure (float) alignment - #'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'letshelp-certbot.tex', u'letshelp-certbot Documentation', - u'Certbot Project', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'letshelp-certbot', u'letshelp-certbot Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'letshelp-certbot', u'letshelp-certbot Documentation', - author, 'letshelp-certbot', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -intersphinx_mapping = { - 'python': ('https://docs.python.org/', None), - 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), - 'certbot': ('https://certbot.eff.org/docs/', None), -} diff --git a/letshelp-certbot/docs/index.rst b/letshelp-certbot/docs/index.rst deleted file mode 100644 index 678d9be2e..000000000 --- a/letshelp-certbot/docs/index.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. letshelp-certbot documentation master file, created by - sphinx-quickstart on Sun Oct 18 13:40:19 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to letshelp-certbot's documentation! -================================================ - -Contents: - -.. toctree:: - :maxdepth: 2 - - -.. toctree:: - :maxdepth: 1 - - api - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/letshelp-certbot/docs/make.bat b/letshelp-certbot/docs/make.bat deleted file mode 100644 index 0229b4f69..000000000 --- a/letshelp-certbot/docs/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\letshelp-certbot.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\letshelp-certbot.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/letshelp-certbot/letshelp_certbot/__init__.py b/letshelp-certbot/letshelp_certbot/__init__.py deleted file mode 100644 index 6882a19d4..000000000 --- a/letshelp-certbot/letshelp_certbot/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tools for submitting server configurations""" diff --git a/letshelp-certbot/letshelp_certbot/apache.py b/letshelp-certbot/letshelp_certbot/apache.py deleted file mode 100755 index a5947399a..000000000 --- a/letshelp-certbot/letshelp_certbot/apache.py +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/env python -"""Certbot Apache configuration submission script""" - -from __future__ import print_function - -import argparse -import atexit -import os -import re -import shutil -import subprocess -import sys -import tarfile -import tempfile -import textwrap - -import six - -from letshelp_certbot.magic_typing import List - -_DESCRIPTION = """ -Let's Help is a simple script you can run to help out the Certbot -project. Since Certbot will support automatically configuring HTTPS on -many servers, we want to test this functionality on as many configurations as -possible. This script will create a sanitized copy of your Apache -configuration, notifying you of the files that have been selected. If (and only -if) you approve this selection, these files will be sent to the Certbot -developers. - -""" - - -_NO_APACHECTL = """ -Unable to find `apachectl` which is required for this script to work. If it is -installed, please run this script again with the --apache-ctl command line -argument and the path to the binary. - -""" - - -# Keywords likely to be found in filenames of sensitive files -_SENSITIVE_FILENAME_REGEX = re.compile(r"^(?!.*proxy_fdpass).*pass.*$|private|" - r"secret|^(?!.*certbot).*cert.*$|crt|" - r"key|rsa|dsa|pw|\.pem|\.der|\.p12|" - r"\.pfx|\.p7b") - - -def make_and_verify_selection(server_root, temp_dir): - """Copies server_root to temp_dir and verifies selection with the user - - :param str server_root: Path to the Apache server root - :param str temp_dir: Path to the temporary directory to copy files to - - """ - copied_files, copied_dirs = copy_config(server_root, temp_dir) - - print(textwrap.fill("A secure copy of the files that have been selected " - "for submission has been created under {0}. All " - "comments have been removed and the files are only " - "accessible by the current user. A list of the files " - "that have been included is shown below. Please make " - "sure that this selection does not contain private " - "keys, passwords, or any other sensitive " - "information.".format(temp_dir))) - print("\nFiles:") - for copied_file in copied_files: - print(copied_file) - print("Directories (including all contained files):") - for copied_dir in copied_dirs: - print(copied_dir) - - sys.stdout.write("\nIs it safe to submit these files? ") - while True: - ans = six.moves.input("(Y)es/(N)o: ").lower() - if ans.startswith("y"): - return - if ans.startswith("n"): - sys.exit("Your files were not submitted") - - -def copy_config(server_root, temp_dir): - """Safely copies server_root to temp_dir and returns copied files - - :param str server_root: Absolute path to the Apache server root - :param str temp_dir: Path to the temporary directory to copy files to - - :returns: List of copied files and a list of leaf directories where - all contained files were copied - :rtype: `tuple` of `list` of `str` - - """ - copied_files = [] # type: List[str] - copied_dirs = [] # type: List[str] - dir_len = len(os.path.dirname(server_root)) - - for config_path, config_dirs, config_files in os.walk(server_root): - temp_path = os.path.join(temp_dir, config_path[dir_len + 1:]) - os.mkdir(temp_path) - - copied_all = True - copied_files_in_current_dir = [] - for config_file in config_files: - config_file_path = os.path.join(config_path, config_file) - temp_file_path = os.path.join(temp_path, config_file) - if os.path.islink(config_file_path): - os.symlink(os.readlink(config_file_path), temp_file_path) - elif safe_config_file(config_file_path): - copy_file_without_comments(config_file_path, temp_file_path) - copied_files_in_current_dir.append(config_file_path) - else: - copied_all = False - - # If copied all files in leaf directory - if copied_all and not config_dirs: - copied_dirs.append(config_path) - else: - copied_files += copied_files_in_current_dir - - return copied_files, copied_dirs - - -def copy_file_without_comments(source, destination): - """Copies source to destination, removing comments - - :param str source: Path to the file to be copied - :param str destination: Path where source should be copied to - - """ - with open(source, "r") as infile: - with open(destination, "w") as outfile: - for line in infile: - if not (line.isspace() or line.lstrip().startswith("#")): - outfile.write(line) - - -def safe_config_file(config_file): - """Returns True if config_file can be safely copied - - :param str config_file: Path to an Apache configuration file - - :returns: True if config_file can be safely copied - :rtype: bool - - """ - config_file_lower = config_file.lower() - if _SENSITIVE_FILENAME_REGEX.search(config_file_lower): - return False - - proc = subprocess.Popen(["file", config_file], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - file_output, _ = proc.communicate() - - if "ASCII" in file_output: - possible_password_file = empty_or_all_comments = True - with open(config_file) as config_fd: - for line in config_fd: - if not (line.isspace() or line.lstrip().startswith("#")): - empty_or_all_comments = False - if line.startswith("-----BEGIN"): - return False - if ":" not in line: - possible_password_file = False - # If file isn't empty or commented out and could be a password file, - # don't include it in selection. It is safe to include the file if - # it consists solely of comments because comments are removed before - # submission. - return empty_or_all_comments or not possible_password_file - - return False - - -def setup_tempdir(args): - """Creates a temporary directory and necessary files for config - - :param argparse.Namespace args: Parsed command line arguments - - :returns: Path to temporary directory - :rtype: str - - """ - tempdir = tempfile.mkdtemp() - - with open(os.path.join(tempdir, "config_file"), "w") as config_fd: - config_fd.write(args.config_file + "\n") - - proc = subprocess.Popen([args.apache_ctl, "-v"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - with open(os.path.join(tempdir, "version"), "w") as version_fd: - version_fd.write(proc.communicate()[0]) - - proc = subprocess.Popen([args.apache_ctl, "-d", args.server_root, "-f", - args.config_file, "-M"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - with open(os.path.join(tempdir, "modules"), "w") as modules_fd: - modules_fd.write(proc.communicate()[0]) - - proc = subprocess.Popen([args.apache_ctl, "-d", args.server_root, "-f", - args.config_file, "-t", "-D", "DUMP_VHOSTS"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - with open(os.path.join(tempdir, "vhosts"), "w") as vhosts_fd: - vhosts_fd.write(proc.communicate()[0]) - - return tempdir - - -def verify_config(args): - """Verifies server_root and config_file specify a valid config - - :param argparse.Namespace args: Parsed command line arguments - - """ - with open(os.devnull, "w") as devnull: - try: - subprocess.check_call([args.apache_ctl, "-d", args.server_root, - "-f", args.config_file, "-t"], - stdout=devnull, stderr=subprocess.STDOUT) - except OSError: - sys.exit(_NO_APACHECTL) - except subprocess.CalledProcessError: - sys.exit("Syntax check from apachectl failed") - - -def locate_config(apache_ctl): - """Uses the apachectl binary to find configuration files - - :param str apache_ctl: Path to `apachectl` binary - - - :returns: Path to Apache server root and main configuration file - :rtype: `tuple` of `str` - - """ - try: - proc = subprocess.Popen([apache_ctl, "-V"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - output, _ = proc.communicate() - except OSError: - sys.exit(_NO_APACHECTL) - - server_root = config_file = "" - for line in output.splitlines(): - # Relevant output lines are of the form: -D DIRECTIVE="VALUE" - if "HTTPD_ROOT" in line: - server_root = line[line.find('"') + 1:-1] - elif "SERVER_CONFIG_FILE" in line: - config_file = line[line.find('"') + 1:-1] - - if not (server_root and config_file): - sys.exit("Unable to locate Apache configuration. Please run this " - "script again and specify --server-root and --config-file") - - return server_root, config_file - - -def get_args(): - """Parses command line arguments - - :returns: Parsed command line options - :rtype: argparse.Namespace - - """ - parser = argparse.ArgumentParser(description=_DESCRIPTION) - parser.add_argument("-c", "--apache-ctl", default="apachectl", - help="path to the `apachectl` binary") - parser.add_argument("-d", "--server-root", - help=("location of the root directory of your Apache " - "configuration")) - parser.add_argument("-f", "--config-file", - help=("location of your main Apache configuration " - "file relative to the server root")) - args = parser.parse_args() - - # args.server_root XOR args.config_file - if bool(args.server_root) != bool(args.config_file): - sys.exit("If either --server-root and --config-file are specified, " - "they both must be included") - elif args.server_root and args.config_file: - args.server_root = os.path.abspath(args.server_root) - args.config_file = os.path.abspath(args.config_file) - - if args.config_file.startswith(args.server_root): - args.config_file = args.config_file[len(args.server_root) + 1:] - else: - sys.exit("This script expects the Apache configuration file to be " - "inside the server root") - - return args - - -def main(): - """Main script execution""" - args = get_args() - if args.server_root is None: - args.server_root, args.config_file = locate_config(args.apache_ctl) - - verify_config(args) - tempdir = setup_tempdir(args) - atexit.register(lambda: shutil.rmtree(tempdir)) - make_and_verify_selection(args.server_root, tempdir) - - tarpath = os.path.join(tempdir, "config.tar.gz") - with tarfile.open(tarpath, mode="w:gz") as tar: - tar.add(tempdir, arcname=".") - - # TODO: Submit tarpath - - -if __name__ == "__main__": - main() # pragma: no cover diff --git a/letshelp-certbot/letshelp_certbot/apache_test.py b/letshelp-certbot/letshelp_certbot/apache_test.py deleted file mode 100644 index 0853046b4..000000000 --- a/letshelp-certbot/letshelp_certbot/apache_test.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Tests for letshelp.letshelp_certbot_apache.py""" -import argparse -import functools -import os -import subprocess -import tarfile -import tempfile -import unittest - -# six is used in mock.patch() -import mock -import pkg_resources -import six # pylint: disable=unused-import - -import letshelp_certbot.apache as letshelp_le_apache - -_PARTIAL_CONF_PATH = os.path.join("mods-available", "ssl.load") -_PARTIAL_LINK_PATH = os.path.join("mods-enabled", "ssl.load") -_CONFIG_FILE = pkg_resources.resource_filename( - __name__, os.path.join("testdata", _PARTIAL_CONF_PATH)) -_PASSWD_FILE = pkg_resources.resource_filename( - __name__, os.path.join("testdata", "uncommonly_named_p4sswd")) -_KEY_FILE = pkg_resources.resource_filename( - __name__, os.path.join("testdata", "uncommonly_named_k3y")) -_SECRET_FILE = pkg_resources.resource_filename( - __name__, os.path.join("testdata", "super_secret_file.txt")) - - -_MODULE_NAME = "letshelp_certbot.apache" - - -_COMPILE_SETTINGS = """Server version: Apache/2.4.10 (Debian) -Server built: Mar 15 2015 09:51:43 -Server's Module Magic Number: 20120211:37 -Server loaded: APR 1.5.1, APR-UTIL 1.5.4 -Compiled using: APR 1.5.1, APR-UTIL 1.5.4 -Architecture: 64-bit -Server MPM: event - threaded: yes (fixed thread count) - forked: yes (variable process count) -Server compiled with.... - -D APR_HAS_SENDFILE - -D APR_HAS_MMAP - -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled) - -D APR_USE_SYSVSEM_SERIALIZE - -D APR_USE_PTHREAD_SERIALIZE - -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT - -D APR_HAS_OTHER_CHILD - -D AP_HAVE_RELIABLE_PIPED_LOGS - -D DYNAMIC_MODULE_LIMIT=256 - -D HTTPD_ROOT="/etc/apache2" - -D SUEXEC_BIN="/usr/lib/apache2/suexec" - -D DEFAULT_PIDLOG="/var/run/apache2.pid" - -D DEFAULT_SCOREBOARD="logs/apache_runtime_status" - -D DEFAULT_ERRORLOG="logs/error_log" - -D AP_TYPES_CONFIG_FILE="mime.types" - -D SERVER_CONFIG_FILE="apache2.conf" - -""" - - -class LetsHelpApacheTest(unittest.TestCase): - @mock.patch(_MODULE_NAME + ".copy_config") - def test_make_and_verify_selection(self, mock_copy_config): - mock_copy_config.return_value = (["apache2.conf"], ["apache2"]) - - with mock.patch("six.moves.input") as mock_input: - with mock.patch(_MODULE_NAME + ".sys.stdout"): - mock_input.side_effect = ["Yes", "No"] - letshelp_le_apache.make_and_verify_selection("root", "temp") - self.assertRaises( - SystemExit, letshelp_le_apache.make_and_verify_selection, - "server_root", "temp_dir") - - def test_copy_config(self): - tempdir = tempfile.mkdtemp() - server_root = pkg_resources.resource_filename(__name__, "testdata") - letshelp_le_apache.copy_config(server_root, tempdir) - - temp_testdata = os.path.join(tempdir, "testdata") - self.assertFalse(os.path.exists(os.path.join( - temp_testdata, os.path.basename(_PASSWD_FILE)))) - self.assertFalse(os.path.exists(os.path.join( - temp_testdata, os.path.basename(_KEY_FILE)))) - self.assertFalse(os.path.exists(os.path.join( - temp_testdata, os.path.basename(_SECRET_FILE)))) - self.assertTrue(os.path.exists(os.path.join( - temp_testdata, _PARTIAL_CONF_PATH))) - self.assertTrue(os.path.exists(os.path.join( - temp_testdata, _PARTIAL_LINK_PATH))) - - def test_copy_file_without_comments(self): - dest = tempfile.mkstemp()[1] - letshelp_le_apache.copy_file_without_comments(_PASSWD_FILE, dest) - - with open(_PASSWD_FILE) as original: - with open(dest) as copy: - for original_line, copied_line in zip(original, copy): - self.assertEqual(original_line, copied_line) - - @mock.patch(_MODULE_NAME + ".subprocess.Popen") - def test_safe_config_file(self, mock_popen): - mock_popen().communicate.return_value = ("PEM RSA private key", None) - self.assertFalse(letshelp_le_apache.safe_config_file("filename")) - - mock_popen().communicate.return_value = ("ASCII text", None) - self.assertFalse(letshelp_le_apache.safe_config_file(_PASSWD_FILE)) - self.assertFalse(letshelp_le_apache.safe_config_file(_KEY_FILE)) - self.assertFalse(letshelp_le_apache.safe_config_file(_SECRET_FILE)) - self.assertTrue(letshelp_le_apache.safe_config_file(_CONFIG_FILE)) - - @mock.patch(_MODULE_NAME + ".subprocess.Popen") - def test_tempdir(self, mock_popen): - mock_popen().communicate.side_effect = [ - ("version", None), ("modules", None), ("vhosts", None)] - args = _get_args() - - tempdir = letshelp_le_apache.setup_tempdir(args) - - with open(os.path.join(tempdir, "config_file")) as config_fd: - self.assertEqual(config_fd.read(), args.config_file + "\n") - - with open(os.path.join(tempdir, "version")) as version_fd: - self.assertEqual(version_fd.read(), "version") - - with open(os.path.join(tempdir, "modules")) as modules_fd: - self.assertEqual(modules_fd.read(), "modules") - - with open(os.path.join(tempdir, "vhosts")) as vhosts_fd: - self.assertEqual(vhosts_fd.read(), "vhosts") - - @mock.patch(_MODULE_NAME + ".subprocess.check_call") - def test_verify_config(self, mock_check_call): - args = _get_args() - mock_check_call.side_effect = [ - None, OSError, subprocess.CalledProcessError(1, "apachectl")] - - letshelp_le_apache.verify_config(args) - self.assertRaises(SystemExit, letshelp_le_apache.verify_config, args) - self.assertRaises(SystemExit, letshelp_le_apache.verify_config, args) - - @mock.patch(_MODULE_NAME + ".subprocess.Popen") - def test_locate_config(self, mock_popen): - mock_popen().communicate.side_effect = [ - OSError, ("bad_output", None), (_COMPILE_SETTINGS, None)] - - self.assertRaises( - SystemExit, letshelp_le_apache.locate_config, "ctl") - self.assertRaises( - SystemExit, letshelp_le_apache.locate_config, "ctl") - server_root, config_file = letshelp_le_apache.locate_config("ctl") - self.assertEqual(server_root, "/etc/apache2") - self.assertEqual(config_file, "apache2.conf") - - @mock.patch(_MODULE_NAME + ".argparse") - def test_get_args(self, mock_argparse): - argv = ["-d", "/etc/apache2"] - mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv) - self.assertRaises(SystemExit, letshelp_le_apache.get_args) - - server_root = "/etc/apache2" - config_file = server_root + "/apache2.conf" - argv = ["-d", server_root, "-f", config_file] - mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv) - args = letshelp_le_apache.get_args() - self.assertEqual(args.apache_ctl, "apachectl") - self.assertEqual(args.server_root, server_root) - self.assertEqual(args.config_file, os.path.basename(config_file)) - - server_root = "/etc/apache2" - config_file = "/etc/httpd/httpd.conf" - argv = ["-d", server_root, "-f", config_file] - mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv) - self.assertRaises(SystemExit, letshelp_le_apache.get_args) - - def test_main_with_args(self): - with mock.patch(_MODULE_NAME + ".get_args"): - self._test_main_common() - - def test_main_without_args(self): - with mock.patch(_MODULE_NAME + ".get_args") as get_args: - args = _get_args() - server_root, config_file = args.server_root, args.config_file - args.server_root = args.config_file = None - get_args.return_value = args - with mock.patch(_MODULE_NAME + ".locate_config") as locate: - locate.return_value = (server_root, config_file) - self._test_main_common() - - def _test_main_common(self): - with mock.patch(_MODULE_NAME + ".verify_config"): - with mock.patch(_MODULE_NAME + ".setup_tempdir") as mock_setup: - tempdir_path = tempfile.mkdtemp() - mock_setup.return_value = tempdir_path - with mock.patch(_MODULE_NAME + ".make_and_verify_selection"): - testdir_basename = "test" - os.mkdir(os.path.join(tempdir_path, testdir_basename)) - - letshelp_le_apache.main() - - tar = tarfile.open(os.path.join( - tempdir_path, "config.tar.gz")) - - tempdir = tar.next() - if tempdir is None: - self.fail("Invalid tarball!") # pragma: no cover - else: - self.assertTrue(tempdir.isdir()) - self.assertEqual(tempdir.name, ".") - - testdir = tar.next() - if testdir is None: - self.fail("Invalid tarball!") # pragma: no cover - else: - self.assertTrue(testdir.isdir()) - self.assertEqual(os.path.basename(testdir.name), - testdir_basename) - - self.assertEqual(tar.next(), None) - - -def _create_mock_parser(argv): - parser = argparse.ArgumentParser() - mock_parser = mock.MagicMock() - mock_parser.add_argument = parser.add_argument - mock_parser.parse_args = functools.partial(parser.parse_args, argv) - - return mock_parser - - -def _get_args(): - args = argparse.Namespace() - args.apache_ctl = "apache_ctl" - args.config_file = "config_file" - args.server_root = "server_root" - - return args - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/letshelp-certbot/letshelp_certbot/magic_typing.py b/letshelp-certbot/letshelp_certbot/magic_typing.py deleted file mode 100644 index d6b1ff056..000000000 --- a/letshelp-certbot/letshelp_certbot/magic_typing.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Shim class to not have to depend on typing module in prod.""" -import sys - - -class TypingClass(object): - """Ignore import errors by getting anything""" - def __getattr__(self, name): - return None - -try: - # mypy doesn't respect modifying sys.modules - from typing import * # pylint: disable=wildcard-import, unused-wildcard-import - from typing import Collection, IO # type: ignore - # pylint: enable=unused-import -except ImportError: - sys.modules[__name__] = TypingClass() diff --git a/letshelp-certbot/letshelp_certbot/magic_typing_test.py b/letshelp-certbot/letshelp_certbot/magic_typing_test.py deleted file mode 100644 index 41bc245fa..000000000 --- a/letshelp-certbot/letshelp_certbot/magic_typing_test.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Tests for letshelp_certbot.magic_typing.""" -import sys -import unittest - -import mock - - -class MagicTypingTest(unittest.TestCase): - """Tests for letshelp_certbot.magic_typing.""" - def test_import_success(self): - try: - import typing as temp_typing - except ImportError: # pragma: no cover - temp_typing = None # pragma: no cover - typing_class_mock = mock.MagicMock() - text_mock = mock.MagicMock() - typing_class_mock.Text = text_mock - sys.modules['typing'] = typing_class_mock - if 'letshelp_certbot.magic_typing' in sys.modules: - del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover - from letshelp_certbot.magic_typing import Text - self.assertEqual(Text, text_mock) - del sys.modules['letshelp_certbot.magic_typing'] - sys.modules['typing'] = temp_typing - - def test_import_failure(self): - try: - import typing as temp_typing - except ImportError: # pragma: no cover - temp_typing = None # pragma: no cover - sys.modules['typing'] = None - if 'letshelp_certbot.magic_typing' in sys.modules: - del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover - from letshelp_certbot.magic_typing import Text - self.assertTrue(Text is None) - del sys.modules['letshelp_certbot.magic_typing'] - sys.modules['typing'] = temp_typing - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/letshelp-certbot/letshelp_certbot/testdata/mods-available/ssl.load b/letshelp-certbot/letshelp_certbot/testdata/mods-available/ssl.load deleted file mode 100644 index 3d2336ae0..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/mods-available/ssl.load +++ /dev/null @@ -1,2 +0,0 @@ -# Depends: setenvif mime socache_shmcb -LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so diff --git a/letshelp-certbot/letshelp_certbot/testdata/mods-enabled/ssl.load b/letshelp-certbot/letshelp_certbot/testdata/mods-enabled/ssl.load deleted file mode 120000 index 9d7972384..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/mods-enabled/ssl.load +++ /dev/null @@ -1 +0,0 @@ -../mods-available/ssl.load \ No newline at end of file diff --git a/letshelp-certbot/letshelp_certbot/testdata/super_secret_file.txt b/letshelp-certbot/letshelp_certbot/testdata/super_secret_file.txt deleted file mode 100644 index 9f592eb7d..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/super_secret_file.txt +++ /dev/null @@ -1 +0,0 @@ -hunter2 diff --git a/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_k3y b/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_k3y deleted file mode 100644 index 659274d1d..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_k3y +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh -AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N -E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 -rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt ------END RSA PRIVATE KEY----- diff --git a/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_p4sswd b/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_p4sswd deleted file mode 100644 index 3559c1d1f..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_p4sswd +++ /dev/null @@ -1 +0,0 @@ -johntheripper:$apr1$fIGE9.JL$jTCwNWZy9Ak/yvOLuOyzQ1 diff --git a/letshelp-certbot/readthedocs.org.requirements.txt b/letshelp-certbot/readthedocs.org.requirements.txt deleted file mode 100644 index b24681caa..000000000 --- a/letshelp-certbot/readthedocs.org.requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e certbot[docs]" (that would in turn install documentation -# dependencies), but it allows to specify a requirements.txt file at -# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) - -# Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e certbot[docs]" must be used instead - --e letshelp-certbot[docs] diff --git a/letshelp-certbot/setup.cfg b/letshelp-certbot/setup.cfg deleted file mode 100644 index 2a9acf13d..000000000 --- a/letshelp-certbot/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/letshelp-certbot/setup.py b/letshelp-certbot/setup.py deleted file mode 100644 index 448c145ce..000000000 --- a/letshelp-certbot/setup.py +++ /dev/null @@ -1,58 +0,0 @@ -from setuptools import find_packages -from setuptools import setup - -version = '0.7.0.dev0' - -install_requires = [ - 'mock', - 'setuptools', # pkg_resources -] - -docs_extras = [ - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] - -setup( - name='letshelp-certbot', - version=version, - description="Let's help Certbot client", - url='https://github.com/letsencrypt/letsencrypt', - author="Certbot Project", - author_email='client-dev@letsencrypt.org', - license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Security', - 'Topic :: System :: Installation/Setup', - 'Topic :: System :: Networking', - 'Topic :: System :: Systems Administration', - 'Topic :: Utilities', - ], - - packages=find_packages(), - include_package_data=True, - install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, - entry_points={ - 'console_scripts': [ - 'letshelp-certbot-apache = letshelp_certbot.apache:main', - ], - }, - test_suite='letshelp_certbot', -) diff --git a/linter_plugin.py b/linter_plugin.py index 1754b1a2a..b6388e2c7 100644 --- a/linter_plugin.py +++ b/linter_plugin.py @@ -10,7 +10,7 @@ from pylint.checkers import BaseChecker from pylint.interfaces import IAstroidChecker # Modules in theses packages can import the os module. -WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'letshelp_certbot', 'lock_test'] +WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'lock_test'] class ForbidStandardOsModule(BaseChecker): diff --git a/mypy.ini b/mypy.ini index 188ed031f..a19fa2a5f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -5,6 +5,3 @@ python_version = 2.7 [mypy-acme.magic_typing_test] ignore_errors = True - -[mypy-letshelp_certbot.magic_typing_test] -ignore_errors = True diff --git a/tools/_venv_common.py b/tools/_venv_common.py index c61385054..75d0d5d33 100644 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -39,7 +39,6 @@ REQUIREMENTS = [ '-e certbot-dns-route53', '-e certbot-dns-sakuracloud', '-e certbot-nginx', - '-e letshelp-certbot', '-e certbot-compatibility-test', '-e certbot-ci', ] diff --git a/tools/install_and_test.py b/tools/install_and_test.py index 192708957..0b47fa5f8 100755 --- a/tools/install_and_test.py +++ b/tools/install_and_test.py @@ -12,7 +12,7 @@ import re import subprocess import sys -SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] +SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache'] def call_with_print(command): diff --git a/tox.cover.py b/tox.cover.py index 0ef5c0d07..3e69a14d6 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -9,7 +9,7 @@ DEFAULT_PACKAGES = [ 'certbot_dns_digitalocean', 'certbot_dns_dnsimple', 'certbot_dns_dnsmadeeasy', 'certbot_dns_gehirn', 'certbot_dns_google', 'certbot_dns_linode', 'certbot_dns_luadns', 'certbot_dns_nsone', 'certbot_dns_ovh', 'certbot_dns_rfc2136', 'certbot_dns_route53', - 'certbot_dns_sakuracloud', 'certbot_nginx', 'letshelp_certbot'] + 'certbot_dns_sakuracloud', 'certbot_nginx'] COVER_THRESHOLDS = { 'certbot': {'linux': 96, 'windows': 96}, @@ -30,10 +30,9 @@ COVER_THRESHOLDS = { 'certbot_dns_route53': {'linux': 92, 'windows': 92}, 'certbot_dns_sakuracloud': {'linux': 97, 'windows': 97}, 'certbot_nginx': {'linux': 97, 'windows': 97}, - 'letshelp_certbot': {'linux': 100, 'windows': 100} } -SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] +SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache'] def cover(package): diff --git a/tox.ini b/tox.ini index b2710ce35..3903cdf45 100644 --- a/tox.ini +++ b/tox.ini @@ -34,8 +34,7 @@ all_packages = certbot[dev] \ certbot-apache \ {[base]dns_packages} \ - certbot-nginx \ - letshelp-certbot + certbot-nginx install_packages = python {toxinidir}/tools/pip_install_editable.py {[base]all_packages} source_paths = @@ -58,7 +57,6 @@ source_paths = certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx - letshelp-certbot/letshelp_certbot tests/lock_test.py [testenv] From 99b1538d0a70986c7e925e8c091b2f36bded9a4b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 18 Feb 2020 11:55:48 -0800 Subject: [PATCH 4/8] Fix spurious pylint errors. (#7780) This fixes (part of) the problem identified in https://github.com/certbot/certbot/pull/7657#issuecomment-586506340. When I tested our pylint setup on Python 3.5.9, 3.6.9, or 3.6.10, tests failed with: ``` ************* Module acme.challenges acme/acme/challenges.py:57:15: E1101: Instance of 'UnrecognizedChallenge' has no 'jobj' member (no-member) ************* Module acme.jws acme/acme/jws.py:28:16: E1101: Class 'Signature' has no '_orig_slots' member (no-member) ``` These errors did not occur for me on Python 3.6.7 or Python 3.7+. You also cannot run our lint setup on Python 2.7 because our pinned version of pylint's dependency `asteroid` does not support Python 2. Because of this, `pylint` is not installed in the virtual environment created by `tools/venv.py` and our [`lint` environment in tox specifies that Python 3 should be used](https://github.com/certbot/certbot/blob/fd64c8c33b2176e6569d64d30776bd5fc9fd3820/tox.ini#L132). I tried updating pylint and its dependencies to fix the problem, but they still occur so I think adding back these disable checks on these lines again is the best fix for now. --- acme/acme/challenges.py | 2 +- acme/acme/jws.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index f3fb19b42..39c8d6269 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -54,7 +54,7 @@ class UnrecognizedChallenge(Challenge): object.__setattr__(self, "jobj", jobj) def to_partial_json(self): - return self.jobj + return self.jobj # pylint: disable=no-member @classmethod def from_json(cls, jobj): diff --git a/acme/acme/jws.py b/acme/acme/jws.py index 9128f56b3..2188c3727 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -25,7 +25,7 @@ class Header(jose.Header): class Signature(jose.Signature): """ACME-specific Signature. Uses ACME-specific Header for customer fields.""" - __slots__ = jose.Signature._orig_slots + __slots__ = jose.Signature._orig_slots # pylint: disable=no-member # TODO: decoder/encoder should accept cls? Otherwise, subclassing # JSONObjectWithFields is tricky... From 42dda355c598d9e1938f6ab4eb30f4fc05b5964f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 18 Feb 2020 14:54:07 -0800 Subject: [PATCH 5/8] Correct AutoHSTS docs (#7767) domains is a list of strings, not a single string. * Correct AutoHSTS docs. * Fix Apache enable_autohsts docs. --- certbot-apache/certbot_apache/_internal/configurator.py | 2 +- certbot/certbot/plugins/enhancements.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index 465237590..8daa28173 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -2471,7 +2471,7 @@ class ApacheConfigurator(common.Installer): :type _unused_lineage: certbot._internal.storage.RenewableCert :param domains: List of domains in certificate to enhance - :type domains: str + :type domains: `list` of `str` """ self._autohsts_fetch_state() diff --git a/certbot/certbot/plugins/enhancements.py b/certbot/certbot/plugins/enhancements.py index be9b7933d..4abce2d2f 100644 --- a/certbot/certbot/plugins/enhancements.py +++ b/certbot/certbot/plugins/enhancements.py @@ -153,7 +153,7 @@ class AutoHSTSEnhancement(object): :type lineage: certbot.interfaces.RenewableCert :param domains: List of domains in certificate to enhance - :type domains: str + :type domains: `list` of `str` """ # This is used to configure internal new style enhancements in Certbot. These From c883efde0f649a0a7f5da1c35cbb1ae54d752109 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 20 Feb 2020 14:35:47 -0800 Subject: [PATCH 6/8] add pgp key docs (#7765) Fixes #7613. --- certbot/docs/packaging.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/certbot/docs/packaging.rst b/certbot/docs/packaging.rst index d2a94dbe7..06ae7bbff 100644 --- a/certbot/docs/packaging.rst +++ b/certbot/docs/packaging.rst @@ -33,6 +33,10 @@ example: `v0.11.1`. .. _`Semantic Versioning`: http://semver.org/ +Our packages are cryptographically signed and their signature can be verified +using the PGP key ``A2CFB51FA275A7286234E7B24D17C995CD9775F2``. This key can be +found on major key servers and at https://dl.eff.org/certbot.pub. + Notes for package maintainers ============================= From 7d79c91e9b52ef75d7ac86232dbbedc84f066587 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 21 Feb 2020 11:18:53 -0800 Subject: [PATCH 7/8] Move our macOS tests to Azure Pipelines (#7793) [Our macOS tests are failing](https://travis-ci.com/certbot/certbot/builds/149965318) again this time due to the problem described at https://travis-ci.community/t/macos-build-fails-because-of-homebrew-bundle-unknown-command/7296/14. I tried adding `update: true` to the Homebrew config as described in that thread, but [it didn't work](https://travis-ci.com/certbot/certbot/builds/150070374). I also tried updating the macOS image we use which [didn't work](https://travis-ci.com/certbot/certbot/builds/150072389). Since we continue to have problems with macOS on Travis, let try moving the tests to Azure Pipelines. * test macos * Remove Travis macOS setup * add displayName --- .azure-pipelines/templates/tests-suite.yml | 24 +++++++++++++++++----- .travis.yml | 19 ----------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/.azure-pipelines/templates/tests-suite.yml b/.azure-pipelines/templates/tests-suite.yml index 119f755a6..069ea94d6 100644 --- a/.azure-pipelines/templates/tests-suite.yml +++ b/.azure-pipelines/templates/tests-suite.yml @@ -1,22 +1,36 @@ jobs: - job: test - pool: - vmImage: vs2017-win2016 strategy: matrix: - py35: + macos-py27: + IMAGE_NAME: macOS-10.14 + PYTHON_VERSION: 2.7 + TOXENV: py27 + macos-py38: + IMAGE_NAME: macOS-10.14 + PYTHON_VERSION: 3.8 + TOXENV: py38 + windows-py35: + IMAGE_NAME: vs2017-win2016 PYTHON_VERSION: 3.5 TOXENV: py35 - py37-cover: + windows-py37-cover: + IMAGE_NAME: vs2017-win2016 PYTHON_VERSION: 3.7 TOXENV: py37-cover - integration-certbot: + windows-integration-certbot: + IMAGE_NAME: vs2017-win2016 PYTHON_VERSION: 3.7 TOXENV: integration-certbot PYTEST_ADDOPTS: --numprocesses 4 + pool: + vmImage: $(IMAGE_NAME) variables: - group: certbot-common steps: + - bash: brew install augeas + condition: startswith(variables['IMAGE_NAME'], 'macOS') + displayName: Install Augeas - task: UsePythonVersion@0 inputs: versionSpec: $(PYTHON_VERSION) diff --git a/.travis.yml b/.travis.yml index 1eae66333..e5354898d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ cache: - $HOME/.cache/pip before_script: - - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ulimit -n 1024 ; fi' # On Travis, the fastest parallelization for integration tests has proved to be 4. - 'if [[ "$TOXENV" == *"integration"* ]]; then export PYTEST_ADDOPTS="--numprocesses 4"; fi' # Use Travis retry feature for farm tests since they are flaky @@ -224,24 +223,6 @@ matrix: packages: # don't install nginx and apache - libaugeas0 <<: *extended-test-suite - - language: generic - env: TOXENV=py27 - os: osx - addons: - homebrew: - packages: - - augeas - - python2 - <<: *extended-test-suite - - language: generic - env: TOXENV=py3 - os: osx - addons: - homebrew: - packages: - - augeas - - python3 - <<: *extended-test-suite # container-based infrastructure sudo: false From 84b57fac9341453b12135cdf26d9ede092e2c3aa Mon Sep 17 00:00:00 2001 From: Raklyon <46962656+Raklyon@users.noreply.github.com> Date: Fri, 21 Feb 2020 21:30:58 +0100 Subject: [PATCH 8/8] Refactor cli.py, splitting in it smaller submodules (#6803) * Refactor cli.py into a package with submodules * Added unit tests for helpful module in cli. * Fixed linter errors * Fixed pylint issues * Updated changelog.md * Fixed test failing and mypy error. Appeared a new pylint error (seems to be in conflict with mypy) mypy require zope.interface to be imported but when imported it is not used and pylint throws an error. * Fixed pylint errors * Apply changes to cli since last merge from master (efc8d49806b14a31d88cfc0f1b6daca1dd373d8d) * Fix lint * Remaining lint errors Co-authored-by: Adrien Ferrand --- certbot/CHANGELOG.md | 2 +- certbot/certbot/_internal/cli.py | 1581 ----------------- certbot/certbot/_internal/cli/__init__.py | 526 ++++++ .../certbot/_internal/cli/cli_constants.py | 107 ++ certbot/certbot/_internal/cli/cli_utils.py | 239 +++ certbot/certbot/_internal/cli/group_adder.py | 19 + certbot/certbot/_internal/cli/helpful.py | 468 +++++ certbot/certbot/_internal/cli/paths_parser.py | 50 + .../certbot/_internal/cli/plugins_parsing.py | 97 + .../cli/report_config_interaction.py | 27 + certbot/certbot/_internal/cli/subparsers.py | 72 + certbot/certbot/_internal/cli/verb_help.py | 106 ++ certbot/tests/cli_test.py | 2 +- certbot/tests/helpful_test.py | 193 ++ 14 files changed, 1906 insertions(+), 1583 deletions(-) delete mode 100644 certbot/certbot/_internal/cli.py create mode 100644 certbot/certbot/_internal/cli/__init__.py create mode 100644 certbot/certbot/_internal/cli/cli_constants.py create mode 100644 certbot/certbot/_internal/cli/cli_utils.py create mode 100644 certbot/certbot/_internal/cli/group_adder.py create mode 100644 certbot/certbot/_internal/cli/helpful.py create mode 100644 certbot/certbot/_internal/cli/paths_parser.py create mode 100644 certbot/certbot/_internal/cli/plugins_parsing.py create mode 100644 certbot/certbot/_internal/cli/report_config_interaction.py create mode 100644 certbot/certbot/_internal/cli/subparsers.py create mode 100644 certbot/certbot/_internal/cli/verb_help.py create mode 100644 certbot/tests/helpful_test.py diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index ff3061e01..126b07eec 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -13,7 +13,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* certbot._internal.cli is now a package split in submodules instead of a whole module. ### Fixed diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py deleted file mode 100644 index c0dbf7424..000000000 --- a/certbot/certbot/_internal/cli.py +++ /dev/null @@ -1,1581 +0,0 @@ -"""Certbot command line argument & config processing.""" -# pylint: disable=too-many-lines -from __future__ import print_function - -import argparse -import copy -import glob -import logging.handlers -import sys - -import configargparse -import six -import zope.component -import zope.interface -from zope.interface import interfaces as zope_interfaces - -from acme import challenges -from acme.magic_typing import Any -from acme.magic_typing import Dict -from acme.magic_typing import Optional -import certbot -from certbot import crypto_util -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot._internal import constants -from certbot._internal import hooks -from certbot._internal.plugins import disco as plugins_disco -import certbot._internal.plugins.selection as plugin_selection -from certbot.compat import os -from certbot.display import util as display_util -import certbot.plugins.enhancements as enhancements - -logger = logging.getLogger(__name__) - -# Global, to save us from a lot of argument passing within the scope of this module -helpful_parser = None # type: Optional[HelpfulArgumentParser] - -# For help strings, figure out how the user ran us. -# When invoked from letsencrypt-auto, sys.argv[0] is something like: -# "/home/user/.local/share/certbot/bin/certbot" -# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before -# running letsencrypt-auto (and sudo stops us from seeing if they did), so it -# should only be used for purposes where inability to detect letsencrypt-auto -# fails safely - -LEAUTO = "letsencrypt-auto" -if "CERTBOT_AUTO" in os.environ: - # if we're here, this is probably going to be certbot-auto, unless the - # user saved the script under a different name - LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"]) - -old_path_fragment = os.path.join(".local", "share", "letsencrypt") -new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt", - "eff.org", "certbot", "venv")) -if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix): - cli_command = LEAUTO -else: - cli_command = "certbot" - -# Argparse's help formatting has a lot of unhelpful peculiarities, so we want -# to replace as much of it as we can... - -# This is the stub to include in help generated by argparse -SHORT_USAGE = """ - {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... - -Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, -it will attempt to use a webserver both for obtaining and installing the -certificate. """.format(cli_command) - -# This section is used for --help and --help all ; it needs information -# about installed plugins to be fully formatted -COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are: - -obtain, install, and renew certificates: - (default) run Obtain & install a certificate in your current webserver - certonly Obtain or renew a certificate, but do not install it - renew Renew all previously obtained certificates that are near expiry - enhance Add security enhancements to your existing configuration - -d DOMAINS Comma-separated list of domains to obtain a certificate for - - %s - --standalone Run a standalone webserver for authentication - %s - --webroot Place files in a server's webroot folder for authentication - --manual Obtain certificates interactively, or using shell script hooks - - -n Run non-interactively - --test-cert Obtain a test certificate from a staging server - --dry-run Test "renew" or "certonly" without saving any certificates to disk - -manage certificates: - certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-name or --cert-path) - delete Delete a certificate (supply --cert-name) - -manage your account: - register Create an ACME account - unregister Deactivate an ACME account - update_account Update an ACME account - --agree-tos Agree to the ACME server's Subscriber Agreement - -m EMAIL Email address for important account notifications -""" - -# This is the short help for certbot --help, where we disable argparse -# altogether -HELP_AND_VERSION_USAGE = """ -More detailed help: - - -h, --help [TOPIC] print this message, or detailed help on a topic; - the available TOPICS are: - - all, automation, commands, paths, security, testing, or any of the - subcommands or plugins (certonly, renew, install, register, nginx, - apache, standalone, webroot, etc.) - -h all print a detailed help page including all topics - --version print the version number -""" - - -# These argparse parameters should be removed when detecting defaults. -ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",) - - -# These sets are used when to help detect options set by the user. -EXIT_ACTIONS = set(("help", "version",)) - - -ZERO_ARG_ACTIONS = set(("store_const", "store_true", - "store_false", "append_const", "count",)) - - -# Maps a config option to a set of config options that may have modified it. -# This dictionary is used recursively, so if A modifies B and B modifies C, -# it is determined that C was modified by the user if A was modified. -VAR_MODIFIERS = {"account": set(("server",)), - "renew_hook": set(("deploy_hook",)), - "server": set(("dry_run", "staging",)), - "webroot_map": set(("webroot_path",))} - - -def report_config_interaction(modified, modifiers): - """Registers config option interaction to be checked by set_by_cli. - - This function can be called by during the __init__ or - add_parser_arguments methods of plugins to register interactions - between config options. - - :param modified: config options that can be modified by modifiers - :type modified: iterable or str (string_types) - :param modifiers: config options that modify modified - :type modifiers: iterable or str (string_types) - - """ - if isinstance(modified, six.string_types): - modified = (modified,) - if isinstance(modifiers, six.string_types): - modifiers = (modifiers,) - - for var in modified: - VAR_MODIFIERS.setdefault(var, set()).update(modifiers) - - -class _Default(object): - """A class to use as a default to detect if a value is set by a user""" - - def __bool__(self): - return False - - def __eq__(self, other): - return isinstance(other, _Default) - - def __hash__(self): - return id(_Default) - - def __nonzero__(self): - return self.__bool__() - - -def set_by_cli(var): - """ - Return True if a particular config variable has been set by the user - (CLI or config file) including if the user explicitly set it to the - default. Returns False if the variable was assigned a default value. - """ - detector = set_by_cli.detector # type: ignore - if detector is None and helpful_parser is not None: - # Setup on first run: `detector` is a weird version of config in which - # the default value of every attribute is wrangled to be boolean-false - plugins = plugins_disco.PluginsRegistry.find_all() - # reconstructed_args == sys.argv[1:], or whatever was passed to main() - reconstructed_args = helpful_parser.args + [helpful_parser.verb] - detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore - plugins, reconstructed_args, detect_defaults=True) - # propagate plugin requests: eg --standalone modifies config.authenticator - detector.authenticator, detector.installer = ( # type: ignore - plugin_selection.cli_plugin_requests(detector)) - - if not isinstance(getattr(detector, var), _Default): - logger.debug("Var %s=%s (set by user).", var, getattr(detector, var)) - return True - - for modifier in VAR_MODIFIERS.get(var, []): - if set_by_cli(modifier): - logger.debug("Var %s=%s (set by user).", - var, VAR_MODIFIERS.get(var, [])) - return True - - return False - -# static housekeeping var -# functions attributed are not supported by mypy -# https://github.com/python/mypy/issues/2087 -set_by_cli.detector = None # type: ignore - - -def has_default_value(option, value): - """Does option have the default value? - - If the default value of option is not known, False is returned. - - :param str option: configuration variable being considered - :param value: value of the configuration variable named option - - :returns: True if option has the default value, otherwise, False - :rtype: bool - - """ - if helpful_parser is not None: - return (option in helpful_parser.defaults and - helpful_parser.defaults[option] == value) - return False - - -def option_was_set(option, value): - """Was option set by the user or does it differ from the default? - - :param str option: configuration variable being considered - :param value: value of the configuration variable named option - - :returns: True if the option was set, otherwise, False - :rtype: bool - - """ - return set_by_cli(option) or not has_default_value(option, value) - - -def argparse_type(variable): - """Return our argparse type function for a config variable (default: str)""" - # pylint: disable=protected-access - if helpful_parser is not None: - for action in helpful_parser.parser._actions: - if action.type is not None and action.dest == variable: - return action.type - return str - -def read_file(filename, mode="rb"): - """Returns the given file's contents. - - :param str filename: path to file - :param str mode: open mode (see `open`) - - :returns: absolute path of filename and its contents - :rtype: tuple - - :raises argparse.ArgumentTypeError: File does not exist or is not readable. - - """ - try: - filename = os.path.abspath(filename) - with open(filename, mode) as the_file: - contents = the_file.read() - return filename, contents - except IOError as exc: - raise argparse.ArgumentTypeError(exc.strerror) - - -def flag_default(name): - """Default value for CLI flag.""" - # XXX: this is an internal housekeeping notion of defaults before - # argparse has been set up; it is not accurate for all flags. Call it - # with caution. Plugin defaults are missing, and some things are using - # defaults defined in this file, not in constants.py :( - return copy.deepcopy(constants.CLI_DEFAULTS[name]) - - -def config_help(name, hidden=False): - """Extract the help message for an `.IConfig` attribute.""" - if hidden: - return argparse.SUPPRESS - field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute - return field.__doc__ - - -class HelpfulArgumentGroup(object): - """Emulates an argparse group for use with HelpfulArgumentParser. - - This class is used in the add_group method of HelpfulArgumentParser. - Command line arguments can be added to the group, but help - suppression and default detection is applied by - HelpfulArgumentParser when necessary. - - """ - def __init__(self, helpful_arg_parser, topic): - self._parser = helpful_arg_parser - self._topic = topic - - def add_argument(self, *args, **kwargs): - """Add a new command line argument to the argument group.""" - self._parser.add(self._topic, *args, **kwargs) - -class CustomHelpFormatter(argparse.HelpFormatter): - """This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes. - - In particular we fix https://bugs.python.org/issue28742 - """ - - def _get_help_string(self, action): - helpstr = action.help - if '%(default)' not in action.help and '(default:' not in action.help: - if action.default != argparse.SUPPRESS: - defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] - if action.option_strings or action.nargs in defaulting_nargs: - helpstr += ' (default: %(default)s)' - return helpstr - -# The attributes here are: -# short: a string that will be displayed by "certbot -h commands" -# opts: a string that heads the section of flags with which this command is documented, -# both for "certbot -h SUBCOMMAND" and "certbot -h all" -# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND" -VERB_HELP = [ - ("run (default)", { - "short": "Obtain/renew a certificate, and install it", - "opts": "Options for obtaining & installing certificates", - "usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""), - "realname": "run" - }), - ("certonly", { - "short": "Obtain or renew a certificate, but do not install it", - "opts": "Options for modifying how a certificate is obtained", - "usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n" - "This command obtains a TLS/SSL certificate without installing it anywhere.") - }), - ("renew", { - "short": "Renew all certificates (or one specified with --cert-name)", - "opts": ("The 'renew' subcommand will attempt to renew all" - " certificates (or more precisely, certificate lineages) you have" - " previously obtained if they are close to expiry, and print a" - " summary of the results. By default, 'renew' will reuse the options" - " used to create obtain or most recently successfully renew each" - " certificate lineage. You can try it with `--dry-run` first. For" - " more fine-grained control, you can renew individual lineages with" - " the `certonly` subcommand. Hooks are available to run commands" - " before and after renewal; see" - " https://certbot.eff.org/docs/using.html#renewal for more" - " information on these."), - "usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n" - }), - ("certificates", { - "short": "List certificates managed by Certbot", - "opts": "List certificates managed by Certbot", - "usage": ("\n\n certbot certificates [options] ...\n\n" - "Print information about the status of certificates managed by Certbot.") - }), - ("delete", { - "short": "Clean up all files related to a certificate", - "opts": "Options for deleting a certificate", - "usage": "\n\n certbot delete --cert-name CERTNAME\n\n" - }), - ("revoke", { - "short": "Revoke a certificate specified with --cert-path or --cert-name", - "opts": "Options for revocation of certificates", - "usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | " - "--cert-name example.com] [options]\n\n" - }), - ("register", { - "short": "Register for account with Let's Encrypt / other ACME server", - "opts": "Options for account registration", - "usage": "\n\n certbot register --email user@example.com [options]\n\n" - }), - ("update_account", { - "short": "Update existing account with Let's Encrypt / other ACME server", - "opts": "Options for account modification", - "usage": "\n\n certbot update_account --email updated_email@example.com [options]\n\n" - }), - ("unregister", { - "short": "Irrevocably deactivate your account", - "opts": "Options for account deactivation.", - "usage": "\n\n certbot unregister [options]\n\n" - }), - ("install", { - "short": "Install an arbitrary certificate in a server", - "opts": "Options for modifying how a certificate is deployed", - "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " - " --key-path /path/to/private-key [options]\n\n" - }), - ("rollback", { - "short": "Roll back server conf changes made during certificate installation", - "opts": "Options for rolling back server configuration changes", - "usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n" - }), - ("plugins", { - "short": "List plugins that are installed and available on your system", - "opts": 'Options for the "plugins" subcommand', - "usage": "\n\n certbot plugins [options]\n\n" - }), - ("update_symlinks", { - "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", - "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " - "or edited a renewal configuration file".format( - os.path.join(flag_default("config_dir"), "live"))), - "usage": "\n\n certbot update_symlinks [options]\n\n" - }), - ("enhance", { - "short": "Add security enhancements to your existing configuration", - "opts": ("Helps to harden the TLS configuration by adding security enhancements " - "to already existing configuration."), - "usage": "\n\n certbot enhance [options]\n\n" - }), - -] -# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful -VERB_HELP_MAP = dict(VERB_HELP) - - -class HelpfulArgumentParser(object): - """Argparse Wrapper. - - This class wraps argparse, adding the ability to make --help less - verbose, and request help on specific subcategories at a time, eg - 'certbot --help security' for security options. - - """ - - - def __init__(self, args, plugins, detect_defaults=False): - from certbot._internal import main - self.VERBS = { - "auth": main.certonly, - "certonly": main.certonly, - "run": main.run, - "install": main.install, - "plugins": main.plugins_cmd, - "register": main.register, - "update_account": main.update_account, - "unregister": main.unregister, - "renew": main.renew, - "revoke": main.revoke, - "rollback": main.rollback, - "everything": main.run, - "update_symlinks": main.update_symlinks, - "certificates": main.certificates, - "delete": main.delete, - "enhance": main.enhance, - } - - # Get notification function for printing - try: - self.notify = zope.component.getUtility( - interfaces.IDisplay).notification - except zope_interfaces.ComponentLookupError: - self.notify = display_util.NoninteractiveDisplay( - sys.stdout).notification - - - # List of topics for which additional help can be provided - HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] - HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] - - plugin_names = list(plugins) - self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore - - self.detect_defaults = detect_defaults - self.args = args - - if self.args and self.args[0] == 'help': - self.args[0] = '--help' - - self.determine_verb() - help1 = self.prescan_for_flag("-h", self.help_topics) - help2 = self.prescan_for_flag("--help", self.help_topics) - if isinstance(help1, bool) and isinstance(help2, bool): - self.help_arg = help1 or help2 - else: - self.help_arg = help1 if isinstance(help1, six.string_types) else help2 - - short_usage = self._usage_string(plugins, self.help_arg) - - self.visible_topics = self.determine_help_topics(self.help_arg) - - # elements are added by .add_group() - self.groups = {} # type: Dict[str, argparse._ArgumentGroup] - # elements are added by .parse_args() - self.defaults = {} # type: Dict[str, Any] - - self.parser = configargparse.ArgParser( - prog="certbot", - usage=short_usage, - formatter_class=CustomHelpFormatter, - args_for_setting_config_path=["-c", "--config"], - default_config_files=flag_default("config_files"), - config_arg_help_message="path to config file (default: {0})".format( - " and ".join(flag_default("config_files")))) - - # This is the only way to turn off overly verbose config flag documentation - self.parser._add_config_file_help = False - - # Help that are synonyms for --help subcommands - COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] - def _list_subcommands(self): - longest = max(len(v) for v in VERB_HELP_MAP) - - text = "The full list of available SUBCOMMANDS is:\n\n" - for verb, props in sorted(VERB_HELP): - doc = props.get("short", "") - text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest) - - text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n" - return text - - def _usage_string(self, plugins, help_arg): - """Make usage strings late so that plugins can be initialised late - - :param plugins: all discovered plugins - :param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC - :rtype: str - :returns: a short usage string for the top of --help TOPIC) - """ - if "nginx" in plugins: - nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" - else: - nginx_doc = "(the certbot nginx plugin is not installed)" - if "apache" in plugins: - apache_doc = "--apache Use the Apache plugin for authentication & installation" - else: - apache_doc = "(the certbot apache plugin is not installed)" - - usage = SHORT_USAGE - if help_arg is True: - self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) - sys.exit(0) - elif help_arg in self.COMMANDS_TOPICS: - self.notify(usage + self._list_subcommands()) - sys.exit(0) - elif help_arg == "all": - # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at - # the top; if we're doing --help someothertopic, it's OT so it's not - usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc) - else: - custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None) - usage = custom if custom else usage - - return usage - - def remove_config_file_domains_for_renewal(self, parsed_args): - """Make "certbot renew" safe if domains are set in cli.ini.""" - # Works around https://github.com/certbot/certbot/issues/4096 - if self.verb == "renew": - for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access - if source.startswith("config_file") and "domains" in flags: - parsed_args.domains = _Default() if self.detect_defaults else [] - - def parse_args(self): - """Parses command line arguments and returns the result. - - :returns: parsed command line arguments - :rtype: argparse.Namespace - - """ - parsed_args = self.parser.parse_args(self.args) - parsed_args.func = self.VERBS[self.verb] - parsed_args.verb = self.verb - - self.remove_config_file_domains_for_renewal(parsed_args) - - if self.detect_defaults: - return parsed_args - - self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key))) - for key in vars(parsed_args)) - - # Do any post-parsing homework here - - if self.verb == "renew": - if parsed_args.force_interactive: - raise errors.Error( - "{0} cannot be used with renew".format( - constants.FORCE_INTERACTIVE_FLAG)) - parsed_args.noninteractive_mode = True - - if parsed_args.force_interactive and parsed_args.noninteractive_mode: - raise errors.Error( - "Flag for non-interactive mode and {0} conflict".format( - constants.FORCE_INTERACTIVE_FLAG)) - - if parsed_args.staging or parsed_args.dry_run: - self.set_test_server(parsed_args) - - if parsed_args.csr: - self.handle_csr(parsed_args) - - if parsed_args.must_staple: - parsed_args.staple = True - - if parsed_args.validate_hooks: - hooks.validate_hooks(parsed_args) - - if parsed_args.allow_subset_of_names: - if any(util.is_wildcard_domain(d) for d in parsed_args.domains): - raise errors.Error("Using --allow-subset-of-names with a" - " wildcard domain is not supported.") - - if parsed_args.hsts and parsed_args.auto_hsts: - raise errors.Error( - "Parameters --hsts and --auto-hsts cannot be used simultaneously.") - - return parsed_args - - def set_test_server(self, parsed_args): - """We have --staging/--dry-run; perform sanity check and set config.server""" - - # Flag combinations should produce these results: - # | --staging | --dry-run | - # ------------------------------------------------------------ - # | --server acme-v02 | Use staging | Use staging | - # | --server acme-staging-v02 | Use staging | Use staging | - # | --server | Conflict error | Use | - - default_servers = (flag_default("server"), constants.STAGING_URI) - - if parsed_args.staging and parsed_args.server not in default_servers: - raise errors.Error("--server value conflicts with --staging") - - if parsed_args.server in default_servers: - parsed_args.server = constants.STAGING_URI - - if parsed_args.dry_run: - if self.verb not in ["certonly", "renew"]: - raise errors.Error("--dry-run currently only works with the " - "'certonly' or 'renew' subcommands (%r)" % self.verb) - parsed_args.break_my_certs = parsed_args.staging = True - if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")): - # The user has a prod account, but might not have a staging - # one; we don't want to start trying to perform interactive registration - parsed_args.tos = True - parsed_args.register_unsafely_without_email = True - - def handle_csr(self, parsed_args): - """Process a --csr flag.""" - if parsed_args.verb != "certonly": - raise errors.Error("Currently, a CSR file may only be specified " - "when obtaining a new or replacement " - "via the certonly command. Please try the " - "certonly command instead.") - if parsed_args.allow_subset_of_names: - raise errors.Error("--allow-subset-of-names cannot be used with --csr") - - csrfile, contents = parsed_args.csr[0:2] - typ, csr, domains = crypto_util.import_csr_file(csrfile, contents) - - # This is not necessary for webroot to work, however, - # obtain_certificate_from_csr requires parsed_args.domains to be set - for domain in domains: - add_domains(parsed_args, domain) - - if not domains: - # TODO: add CN to domains instead: - raise errors.Error( - "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" - % parsed_args.csr[0]) - - parsed_args.actual_csr = (csr, typ) - - csr_domains = {d.lower() for d in domains} - config_domains = set(parsed_args.domains) - if csr_domains != config_domains: - raise errors.ConfigurationError( - "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" - .format(", ".join(csr_domains), ", ".join(config_domains))) - - - def determine_verb(self): - """Determines the verb/subcommand provided by the user. - - This function works around some of the limitations of argparse. - - """ - if "-h" in self.args or "--help" in self.args: - # all verbs double as help arguments; don't get them confused - self.verb = "help" - return - - for i, token in enumerate(self.args): - if token in self.VERBS: - verb = token - if verb == "auth": - verb = "certonly" - if verb == "everything": - verb = "run" - self.verb = verb - self.args.pop(i) - return - - self.verb = "run" - - def prescan_for_flag(self, flag, possible_arguments): - """Checks cli input for flags. - - Check for a flag, which accepts a fixed set of possible arguments, in - the command line; we will use this information to configure argparse's - help correctly. Return the flag's argument, if it has one that matches - the sequence @possible_arguments; otherwise return whether the flag is - present. - - """ - if flag not in self.args: - return False - pos = self.args.index(flag) - try: - nxt = self.args[pos + 1] - if nxt in possible_arguments: - return nxt - except IndexError: - pass - return True - - def add(self, topics, *args, **kwargs): - """Add a new command line argument. - - :param topics: str or [str] help topic(s) this should be listed under, - or None for options that don't fit under a specific - topic which will only be shown in "--help all" output. - The first entry determines where the flag lives in the - "--help all" output (None -> "optional arguments"). - :param list *args: the names of this argument flag - :param dict **kwargs: various argparse settings for this argument - - """ - - if isinstance(topics, list): - # if this flag can be listed in multiple sections, try to pick the one - # that the user has asked for help about - topic = self.help_arg if self.help_arg in topics else topics[0] - else: - topic = topics # there's only one - - if self.detect_defaults: - kwargs = self.modify_kwargs_for_default_detection(**kwargs) - - if self.visible_topics[topic]: - if topic in self.groups: - group = self.groups[topic] - group.add_argument(*args, **kwargs) - else: - self.parser.add_argument(*args, **kwargs) - else: - kwargs["help"] = argparse.SUPPRESS - self.parser.add_argument(*args, **kwargs) - - def modify_kwargs_for_default_detection(self, **kwargs): - """Modify an arg so we can check if it was set by the user. - - Changes the parameters given to argparse when adding an argument - so we can properly detect if the value was set by the user. - - :param dict kwargs: various argparse settings for this argument - - :returns: a modified versions of kwargs - :rtype: dict - - """ - action = kwargs.get("action", None) - if action not in EXIT_ACTIONS: - kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else - "store") - kwargs["default"] = _Default() - for param in ARGPARSE_PARAMS_TO_REMOVE: - kwargs.pop(param, None) - - return kwargs - - def add_deprecated_argument(self, argument_name, num_args): - """Adds a deprecated argument with the name argument_name. - - Deprecated arguments are not shown in the help. If they are used - on the command line, a warning is shown stating that the - argument is deprecated and no other action is taken. - - :param str argument_name: Name of deprecated argument. - :param int nargs: Number of arguments the option takes. - - """ - util.add_deprecated_argument( - self.parser.add_argument, argument_name, num_args) - - def add_group(self, topic, verbs=(), **kwargs): - """Create a new argument group. - - This method must be called once for every topic, however, calls - to this function are left next to the argument definitions for - clarity. - - :param str topic: Name of the new argument group. - :param str verbs: List of subcommands that should be documented as part of - this help group / topic - - :returns: The new argument group. - :rtype: `HelpfulArgumentGroup` - - """ - if self.visible_topics[topic]: - self.groups[topic] = self.parser.add_argument_group(topic, **kwargs) - if self.help_arg: - for v in verbs: - self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) - return HelpfulArgumentGroup(self, topic) - - def add_plugin_args(self, plugins): - """ - - Let each of the plugins add its own command line arguments, which - may or may not be displayed as help topics. - - """ - for name, plugin_ep in six.iteritems(plugins): - parser_or_group = self.add_group(name, - description=plugin_ep.long_description) - plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) - - def determine_help_topics(self, chosen_topic): - """ - - The user may have requested help on a topic, return a dict of which - topics to display. @chosen_topic has prescan_for_flag's return type - - :returns: dict - - """ - # topics maps each topic to whether it should be documented by - # argparse on the command line - if chosen_topic == "auth": - chosen_topic = "certonly" - if chosen_topic == "everything": - chosen_topic = "run" - if chosen_topic == "all": - # Addition of condition closes #6209 (removal of duplicate route53 option). - return {t: t != 'certbot-route53:auth' for t in self.help_topics} - elif not chosen_topic: - return {t: False for t in self.help_topics} - return {t: t == chosen_topic for t in self.help_topics} - - -def _add_all_groups(helpful): - helpful.add_group("automation", description="Flags for automating execution & other tweaks") - helpful.add_group("security", description="Security parameters & server settings") - helpful.add_group("testing", - description="The following flags are meant for testing and integration purposes only.") - helpful.add_group("paths", description="Flags for changing execution paths & servers") - helpful.add_group("manage", - description="Various subcommands and flags are available for managing your certificates:", - verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) - - # VERBS - for verb, docs in VERB_HELP: - name = docs.get("realname", verb) - helpful.add_group(name, description=docs["opts"]) - - -def prepare_and_parse_args(plugins, args, detect_defaults=False): - """Returns parsed command line arguments. - - :param .PluginsRegistry plugins: available plugins - :param list args: command line arguments with the program name removed - - :returns: parsed command line arguments - :rtype: argparse.Namespace - - """ - - - helpful = HelpfulArgumentParser(args, plugins, detect_defaults) - _add_all_groups(helpful) - - # --help is automatically provided by argparse - helpful.add( - None, "-v", "--verbose", dest="verbose_count", action="count", - default=flag_default("verbose_count"), help="This flag can be used " - "multiple times to incrementally increase the verbosity of output, " - "e.g. -vvv.") - helpful.add( - None, "-t", "--text", dest="text_mode", action="store_true", - default=flag_default("text_mode"), help=argparse.SUPPRESS) - helpful.add( - None, "--max-log-backups", type=nonnegative_int, - default=flag_default("max_log_backups"), - help="Specifies the maximum number of backup logs that should " - "be kept by Certbot's built in log rotation. Setting this " - "flag to 0 disables log rotation entirely, causing " - "Certbot to always append to the same log file.") - helpful.add( - [None, "automation", "run", "certonly", "enhance"], - "-n", "--non-interactive", "--noninteractive", - dest="noninteractive_mode", action="store_true", - default=flag_default("noninteractive_mode"), - help="Run without ever asking for user input. This may require " - "additional command line flags; the client will try to explain " - "which ones are required if it finds one missing") - helpful.add( - [None, "register", "run", "certonly", "enhance"], - constants.FORCE_INTERACTIVE_FLAG, action="store_true", - default=flag_default("force_interactive"), - help="Force Certbot to be interactive even if it detects it's not " - "being run in a terminal. This flag cannot be used with the " - "renew subcommand.") - helpful.add( - [None, "run", "certonly", "certificates", "enhance"], - "-d", "--domains", "--domain", dest="domains", - metavar="DOMAIN", action=_DomainsAction, - default=flag_default("domains"), - help="Domain names to apply. For multiple domains you can use " - "multiple -d flags or enter a comma separated list of domains " - "as a parameter. The first domain provided will be the " - "subject CN of the certificate, and all domains will be " - "Subject Alternative Names on the certificate. " - "The first domain will also be used in " - "some software user interfaces and as the file paths for the " - "certificate and related material unless otherwise " - "specified or you already have a certificate with the same " - "name. In the case of a name collision it will append a number " - "like 0001 to the file path name. (default: Ask)") - helpful.add( - [None, "run", "certonly", "register"], - "--eab-kid", dest="eab_kid", - metavar="EAB_KID", - help="Key Identifier for External Account Binding" - ) - helpful.add( - [None, "run", "certonly", "register"], - "--eab-hmac-key", dest="eab_hmac_key", - metavar="EAB_HMAC_KEY", - help="HMAC key for External Account Binding" - ) - helpful.add( - [None, "run", "certonly", "manage", "delete", "certificates", - "renew", "enhance"], "--cert-name", dest="certname", - metavar="CERTNAME", default=flag_default("certname"), - help="Certificate name to apply. This name is used by Certbot for housekeeping " - "and in file paths; it doesn't affect the content of the certificate itself. " - "To see certificate names, run 'certbot certificates'. " - "When creating a new certificate, specifies the new certificate's name. " - "(default: the first provided domain or the name of an existing " - "certificate on your system for the same domains)") - helpful.add( - [None, "testing", "renew", "certonly"], - "--dry-run", action="store_true", dest="dry_run", - default=flag_default("dry_run"), - help="Perform a test run of the client, obtaining test (invalid) certificates" - " but not saving them to disk. This can currently only be used" - " with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run" - " tries to avoid making any persistent changes on a system, it " - " is not completely side-effect free: if used with webserver authenticator plugins" - " like apache and nginx, it makes and then reverts temporary config changes" - " in order to obtain test certificates, and reloads webservers to deploy and then" - " roll back those changes. It also calls --pre-hook and --post-hook commands" - " if they are defined because they may be necessary to accurately simulate" - " renewal. --deploy-hook commands are not called.") - helpful.add( - ["register", "automation"], "--register-unsafely-without-email", action="store_true", - default=flag_default("register_unsafely_without_email"), - help="Specifying this flag enables registering an account with no " - "email address. This is strongly discouraged, because in the " - "event of key loss or account compromise you will irrevocably " - "lose access to your account. You will also be unable to receive " - "notice about impending expiration or revocation of your " - "certificates. Updates to the Subscriber Agreement will still " - "affect you, and will be effective 14 days after posting an " - "update to the web site.") - helpful.add( - ["register", "update_account", "unregister", "automation"], "-m", "--email", - default=flag_default("email"), - help=config_help("email")) - helpful.add(["register", "update_account", "automation"], "--eff-email", action="store_true", - default=flag_default("eff_email"), dest="eff_email", - help="Share your e-mail address with EFF") - helpful.add(["register", "update_account", "automation"], "--no-eff-email", - action="store_false", default=flag_default("eff_email"), dest="eff_email", - help="Don't share your e-mail address with EFF") - helpful.add( - ["automation", "certonly", "run"], - "--keep-until-expiring", "--keep", "--reinstall", - dest="reinstall", action="store_true", default=flag_default("reinstall"), - help="If the requested certificate matches an existing certificate, always keep the " - "existing one until it is due for renewal (for the " - "'run' subcommand this means reinstall the existing certificate). (default: Ask)") - helpful.add( - "automation", "--expand", action="store_true", default=flag_default("expand"), - help="If an existing certificate is a strict subset of the requested names, " - "always expand and replace it with the additional names. (default: Ask)") - helpful.add( - "automation", "--version", action="version", - version="%(prog)s {0}".format(certbot.__version__), - help="show program's version number and exit") - helpful.add( - ["automation", "renew"], - "--force-renewal", "--renew-by-default", dest="renew_by_default", - action="store_true", default=flag_default("renew_by_default"), - help="If a certificate " - "already exists for the requested domains, renew it now, " - "regardless of whether it is near expiry. (Often " - "--keep-until-expiring is more appropriate). Also implies " - "--expand.") - helpful.add( - "automation", "--renew-with-new-domains", dest="renew_with_new_domains", - action="store_true", default=flag_default("renew_with_new_domains"), - help="If a " - "certificate already exists for the requested certificate name " - "but does not match the requested domains, renew it now, " - "regardless of whether it is near expiry.") - helpful.add( - "automation", "--reuse-key", dest="reuse_key", - action="store_true", default=flag_default("reuse_key"), - help="When renewing, use the same private key as the existing " - "certificate.") - - helpful.add( - ["automation", "renew", "certonly"], - "--allow-subset-of-names", action="store_true", - default=flag_default("allow_subset_of_names"), - help="When performing domain validation, do not consider it a failure " - "if authorizations can not be obtained for a strict subset of " - "the requested domains. This may be useful for allowing renewals for " - "multiple domains to succeed even if some domains no longer point " - "at this system. This option cannot be used with --csr.") - helpful.add( - "automation", "--agree-tos", dest="tos", action="store_true", - default=flag_default("tos"), - help="Agree to the ACME Subscriber Agreement (default: Ask)") - helpful.add( - ["unregister", "automation"], "--account", metavar="ACCOUNT_ID", - default=flag_default("account"), - help="Account ID to use") - helpful.add( - "automation", "--duplicate", dest="duplicate", action="store_true", - default=flag_default("duplicate"), - help="Allow making a certificate lineage that duplicates an existing one " - "(both can be renewed in parallel)") - helpful.add( - "automation", "--os-packages-only", action="store_true", - default=flag_default("os_packages_only"), - help="(certbot-auto only) install OS package dependencies and then stop") - helpful.add( - "automation", "--no-self-upgrade", action="store_true", - default=flag_default("no_self_upgrade"), - help="(certbot-auto only) prevent the certbot-auto script from" - " upgrading itself to newer released versions (default: Upgrade" - " automatically)") - helpful.add( - "automation", "--no-bootstrap", action="store_true", - default=flag_default("no_bootstrap"), - help="(certbot-auto only) prevent the certbot-auto script from" - " installing OS-level dependencies (default: Prompt to install " - " OS-wide dependencies, but exit if the user says 'No')") - helpful.add( - "automation", "--no-permissions-check", action="store_true", - default=flag_default("no_permissions_check"), - help="(certbot-auto only) skip the check on the file system" - " permissions of the certbot-auto script") - helpful.add( - ["automation", "renew", "certonly", "run"], - "-q", "--quiet", dest="quiet", action="store_true", - default=flag_default("quiet"), - help="Silence all output except errors. Useful for automation via cron." - " Implies --non-interactive.") - # overwrites server, handled in HelpfulArgumentParser.parse_args() - helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging", - dest="staging", action="store_true", default=flag_default("staging"), - help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent" - " to --server " + constants.STAGING_URI) - helpful.add( - "testing", "--debug", action="store_true", default=flag_default("debug"), - help="Show tracebacks in case of errors, and allow certbot-auto " - "execution on experimental platforms") - helpful.add( - [None, "certonly", "run"], "--debug-challenges", action="store_true", - default=flag_default("debug_challenges"), - help="After setting up challenges, wait for user input before " - "submitting to CA") - helpful.add( - "testing", "--no-verify-ssl", action="store_true", - help=config_help("no_verify_ssl"), - default=flag_default("no_verify_ssl")) - helpful.add( - ["testing", "standalone", "manual"], "--http-01-port", type=int, - dest="http01_port", - default=flag_default("http01_port"), help=config_help("http01_port")) - helpful.add( - ["testing", "standalone"], "--http-01-address", - dest="http01_address", - default=flag_default("http01_address"), help=config_help("http01_address")) - helpful.add( - ["testing", "nginx"], "--https-port", type=int, - default=flag_default("https_port"), - help=config_help("https_port")) - helpful.add( - "testing", "--break-my-certs", action="store_true", - default=flag_default("break_my_certs"), - help="Be willing to replace or renew valid certificates with invalid " - "(testing/staging) certificates") - helpful.add( - "security", "--rsa-key-size", type=int, metavar="N", - default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) - helpful.add( - "security", "--must-staple", action="store_true", - dest="must_staple", default=flag_default("must_staple"), - help=config_help("must_staple")) - helpful.add( - ["security", "enhance"], - "--redirect", action="store_true", dest="redirect", - default=flag_default("redirect"), - help="Automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost. (default: Ask)") - helpful.add( - "security", "--no-redirect", action="store_false", dest="redirect", - default=flag_default("redirect"), - help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost. (default: Ask)") - helpful.add( - ["security", "enhance"], - "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"), - help="Add the Strict-Transport-Security header to every HTTP response." - " Forcing browser to always use SSL for the domain." - " Defends against SSL Stripping.") - helpful.add( - "security", "--no-hsts", action="store_false", dest="hsts", - default=flag_default("hsts"), help=argparse.SUPPRESS) - helpful.add( - ["security", "enhance"], - "--uir", action="store_true", dest="uir", default=flag_default("uir"), - help='Add the "Content-Security-Policy: upgrade-insecure-requests"' - ' header to every HTTP response. Forcing the browser to use' - ' https:// for every http:// resource.') - helpful.add( - "security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"), - help=argparse.SUPPRESS) - helpful.add( - "security", "--staple-ocsp", action="store_true", dest="staple", - default=flag_default("staple"), - help="Enables OCSP Stapling. A valid OCSP response is stapled to" - " the certificate that the server offers during TLS.") - helpful.add( - "security", "--no-staple-ocsp", action="store_false", dest="staple", - default=flag_default("staple"), help=argparse.SUPPRESS) - helpful.add( - "security", "--strict-permissions", action="store_true", - default=flag_default("strict_permissions"), - help="Require that all configuration files are owned by the current " - "user; only needed if your config is somewhere unsafe like /tmp/") - helpful.add( - ["manual", "standalone", "certonly", "renew"], - "--preferred-challenges", dest="pref_challs", - action=_PrefChallAction, default=flag_default("pref_challs"), - help='A sorted, comma delimited list of the preferred challenge to ' - 'use during authorization with the most preferred challenge ' - 'listed first (Eg, "dns" or "http,dns"). ' - 'Not all plugins support all challenges. See ' - 'https://certbot.eff.org/docs/using.html#plugins for details. ' - 'ACME Challenges are versioned, but if you pick "http" rather ' - 'than "http-01", Certbot will select the latest version ' - 'automatically.') - helpful.add( - "renew", "--pre-hook", - help="Command to be run in a shell before obtaining any certificates." - " Intended primarily for renewal, where it can be used to temporarily" - " shut down a webserver that might conflict with the standalone" - " plugin. This will only be called if a certificate is actually to be" - " obtained/renewed. When renewing several certificates that have" - " identical pre-hooks, only the first will be executed.") - helpful.add( - "renew", "--post-hook", - help="Command to be run in a shell after attempting to obtain/renew" - " certificates. Can be used to deploy renewed certificates, or to" - " restart any servers that were stopped by --pre-hook. This is only" - " run if an attempt was made to obtain/renew a certificate. If" - " multiple renewed certificates have identical post-hooks, only" - " one will be run.") - helpful.add("renew", "--renew-hook", - action=_RenewHookAction, help=argparse.SUPPRESS) - helpful.add( - "renew", "--no-random-sleep-on-renew", action="store_false", - default=flag_default("random_sleep_on_renew"), dest="random_sleep_on_renew", - help=argparse.SUPPRESS) - helpful.add( - "renew", "--deploy-hook", action=_DeployHookAction, - help='Command to be run in a shell once for each successfully' - ' issued certificate. For this command, the shell variable' - ' $RENEWED_LINEAGE will point to the config live subdirectory' - ' (for example, "/etc/letsencrypt/live/example.com") containing' - ' the new certificates and keys; the shell variable' - ' $RENEWED_DOMAINS will contain a space-delimited list of' - ' renewed certificate domains (for example, "example.com' - ' www.example.com"') - helpful.add( - "renew", "--disable-hook-validation", - action="store_false", dest="validate_hooks", - default=flag_default("validate_hooks"), - help="Ordinarily the commands specified for" - " --pre-hook/--post-hook/--deploy-hook will be checked for" - " validity, to see if the programs being run are in the $PATH," - " so that mistakes can be caught early, even when the hooks" - " aren't being run just yet. The validation is rather" - " simplistic and fails if you use more advanced shell" - " constructs, so you can use this switch to disable it." - " (default: False)") - helpful.add( - "renew", "--no-directory-hooks", action="store_false", - default=flag_default("directory_hooks"), dest="directory_hooks", - help="Disable running executables found in Certbot's hook directories" - " during renewal. (default: False)") - helpful.add( - "renew", "--disable-renew-updates", action="store_true", - default=flag_default("disable_renew_updates"), dest="disable_renew_updates", - help="Disable automatic updates to your server configuration that" - " would otherwise be done by the selected installer plugin, and triggered" - " when the user executes \"certbot renew\", regardless of if the certificate" - " is renewed. This setting does not apply to important TLS configuration" - " updates.") - helpful.add( - "renew", "--no-autorenew", action="store_false", - default=flag_default("autorenew"), dest="autorenew", - help="Disable auto renewal of certificates.") - - # Populate the command line parameters for new style enhancements - enhancements.populate_cli(helpful.add) - - _create_subparsers(helpful) - _paths_parser(helpful) - # _plugins_parsing should be the last thing to act upon the main - # parser (--help should display plugin-specific options last) - _plugins_parsing(helpful, plugins) - - if not detect_defaults: - global helpful_parser # pylint: disable=global-statement - helpful_parser = helpful - return helpful.parse_args() - - -def _create_subparsers(helpful): - from certbot._internal.client import sample_user_agent # avoid import loops - helpful.add( - None, "--user-agent", default=flag_default("user_agent"), - help='Set a custom user agent string for the client. User agent strings allow ' - 'the CA to collect high level statistics about success rates by OS, ' - 'plugin and use 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: {0}). The flags encoded in the user agent are: ' - '--duplicate, --force-renew, --allow-subset-of-names, -n, and ' - 'whether any hooks are set.'.format(sample_user_agent())) - helpful.add( - None, "--user-agent-comment", default=flag_default("user_agent_comment"), - type=_user_agent_comment_type, - help="Add a comment to the default user agent string. May be used when repackaging Certbot " - "or calling it from another tool to allow additional statistical data to be collected." - " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") - helpful.add("certonly", - "--csr", default=flag_default("csr"), type=read_file, - help="Path to a Certificate Signing Request (CSR) in DER or PEM format." - " 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)), - action=_EncodeReasonAction, default=flag_default("reason"), - help="Specify reason for revoking certificate. (default: unspecified)") - helpful.add("revoke", - "--delete-after-revoke", action="store_true", - default=flag_default("delete_after_revoke"), - help="Delete certificates after revoking them, along with all previous and later " - "versions of those certificates.") - helpful.add("revoke", - "--no-delete-after-revoke", action="store_false", - dest="delete_after_revoke", - default=flag_default("delete_after_revoke"), - help="Do not delete certificates after revoking them. This " - "option should be used with caution because the 'renew' " - "subcommand will attempt to renew undeleted revoked " - "certificates.") - helpful.add("rollback", - "--checkpoints", type=int, metavar="N", - default=flag_default("rollback_checkpoints"), - help="Revert configuration N number of checkpoints.") - helpful.add("plugins", - "--init", action="store_true", default=flag_default("init"), - help="Initialize plugins.") - helpful.add("plugins", - "--prepare", action="store_true", default=flag_default("prepare"), - help="Initialize and prepare plugins.") - helpful.add("plugins", - "--authenticators", action="append_const", dest="ifaces", - default=flag_default("ifaces"), - const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.") - helpful.add("plugins", - "--installers", action="append_const", dest="ifaces", - default=flag_default("ifaces"), - const=interfaces.IInstaller, help="Limit to installer plugins only.") - - -class CaseInsensitiveList(list): - """A list that will ignore case when searching. - - This class is passed to the `choices` argument of `argparse.add_arguments` - 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()) - - -def _paths_parser(helpful): - add = helpful.add - verb = helpful.verb - if verb == "help": - verb = helpful.help_arg - - cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." - sections = ["paths", "install", "revoke", "certonly", "manage"] - if verb == "certonly": - add(sections, "--cert-path", type=os.path.abspath, - default=flag_default("auth_cert_path"), help=cph) - elif verb == "revoke": - add(sections, "--cert-path", type=read_file, required=False, help=cph) - else: - add(sections, "--cert-path", type=os.path.abspath, help=cph) - - section = "paths" - if verb in ("install", "revoke"): - section = verb - # revoke --key-path reads a file, install --key-path takes a string - add(section, "--key-path", - type=((verb == "revoke" and read_file) or os.path.abspath), - help="Path to private key for certificate installation " - "or revocation (if account key is missing)") - - default_cp = None - if verb == "certonly": - default_cp = flag_default("auth_chain_path") - add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath, - help="Accompanying path to a full certificate chain (certificate plus chain).") - add("paths", "--chain-path", default=default_cp, type=os.path.abspath, - help="Accompanying path to a certificate chain.") - add("paths", "--config-dir", default=flag_default("config_dir"), - help=config_help("config_dir")) - add("paths", "--work-dir", default=flag_default("work_dir"), - help=config_help("work_dir")) - add("paths", "--logs-dir", default=flag_default("logs_dir"), - help="Logs directory.") - add("paths", "--server", default=flag_default("server"), - help=config_help("server")) - - -def _plugins_parsing(helpful, plugins): - # It's nuts, but there are two "plugins" topics. Somehow this works - helpful.add_group( - "plugins", description="Plugin Selection: Certbot client supports an " - "extensible plugins architecture. See '%(prog)s plugins' for a " - "list of all installed plugins and their names. You can force " - "a particular plugin by setting options provided below. Running " - "--help will list flags specific to that plugin.") - - helpful.add("plugins", "--configurator", default=flag_default("configurator"), - help="Name of the plugin that is both an authenticator and an installer." - " Should not be used together with --authenticator or --installer. " - "(default: Ask)") - helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), - help="Authenticator plugin name.") - helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), - help="Installer plugin name (also used to find domains).") - helpful.add(["plugins", "certonly", "run", "install"], - "--apache", action="store_true", default=flag_default("apache"), - help="Obtain and install certificates using Apache") - helpful.add(["plugins", "certonly", "run", "install"], - "--nginx", action="store_true", default=flag_default("nginx"), - help="Obtain and install certificates using Nginx") - helpful.add(["plugins", "certonly"], "--standalone", action="store_true", - default=flag_default("standalone"), - help='Obtain certificates using a "standalone" webserver.') - helpful.add(["plugins", "certonly"], "--manual", action="store_true", - default=flag_default("manual"), - help="Provide laborious manual instructions for obtaining a certificate") - helpful.add(["plugins", "certonly"], "--webroot", action="store_true", - default=flag_default("webroot"), - help="Obtain certificates by placing files in a webroot directory.") - helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", - default=flag_default("dns_cloudflare"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Cloudflare for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true", - default=flag_default("dns_cloudxns"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using CloudXNS for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true", - default=flag_default("dns_digitalocean"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DigitalOcean for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true", - default=flag_default("dns_dnsimple"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DNSimple for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", - default=flag_default("dns_dnsmadeeasy"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DNS Made Easy for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", - default=flag_default("dns_gehirn"), - help=("Obtain certificates using a DNS TXT record " - "(if you are using Gehirn Infrastructure Service for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", - default=flag_default("dns_google"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Google Cloud DNS).")) - helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true", - default=flag_default("dns_linode"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Linode for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true", - default=flag_default("dns_luadns"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using LuaDNS for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", - default=flag_default("dns_nsone"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using NS1 for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true", - default=flag_default("dns_ovh"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using OVH for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true", - default=flag_default("dns_rfc2136"), - help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).") - helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true", - default=flag_default("dns_route53"), - help=("Obtain certificates using a DNS TXT record (if you are using Route53 for " - "DNS).")) - helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true", - default=flag_default("dns_sakuracloud"), - help=("Obtain certificates using a DNS TXT record " - "(if you are using Sakura Cloud for DNS).")) - - # things should not be reorder past/pre this comment: - # plugins_group should be displayed in --help before plugin - # specific groups (so that plugins_group.description makes sense) - - helpful.add_plugin_args(plugins) - - -class _EncodeReasonAction(argparse.Action): - """Action class for parsing revocation reason.""" - - def __call__(self, parser, namespace, reason, option_string=None): - """Encodes the reason for certificate revocation.""" - code = constants.REVOCATION_REASONS[reason.lower()] - setattr(namespace, self.dest, code) - - -class _DomainsAction(argparse.Action): - """Action class for parsing domains.""" - - def __call__(self, parser, namespace, domain, option_string=None): - """Just wrap add_domains in argparseese.""" - add_domains(namespace, domain) - -def add_domains(args_or_config, domains): - """Registers new domains to be used during the current client run. - - Domains are not added to the list of requested domains if they have - already been registered. - - :param args_or_config: parsed command line arguments - :type args_or_config: argparse.Namespace or - configuration.NamespaceConfig - :param str domain: one or more comma separated domains - - :returns: domains after they have been normalized and validated - :rtype: `list` of `str` - - """ - validated_domains = [] - for domain in domains.split(","): - domain = util.enforce_domain_sanity(domain.strip()) - validated_domains.append(domain) - if domain not in args_or_config.domains: - args_or_config.domains.append(domain) - - return validated_domains - -class _PrefChallAction(argparse.Action): - """Action class for parsing preferred challenges.""" - - def __call__(self, parser, namespace, pref_challs, option_string=None): - try: - challs = parse_preferred_challenges(pref_challs.split(",")) - except errors.Error as error: - raise argparse.ArgumentError(self, str(error)) - namespace.pref_challs.extend(challs) - - -def parse_preferred_challenges(pref_challs): - """Translate and validate preferred challenges. - - :param pref_challs: list of preferred challenge types - :type pref_challs: `list` of `str` - - :returns: validated list of preferred challenge types - :rtype: `list` of `str` - - :raises errors.Error: if pref_challs is invalid - - """ - aliases = {"dns": "dns-01", "http": "http-01"} - challs = [c.strip() for c in pref_challs] - challs = [aliases.get(c, c) for c in challs] - - unrecognized = ", ".join(name for name in challs - if name not in challenges.Challenge.TYPES) - if unrecognized: - raise errors.Error( - "Unrecognized challenges: {0}".format(unrecognized)) - return challs - - -def _user_agent_comment_type(value): - if "(" in value or ")" in value: - raise argparse.ArgumentTypeError("may not contain parentheses") - return value - - -class _DeployHookAction(argparse.Action): - """Action class for parsing deploy hooks.""" - - def __call__(self, parser, namespace, values, option_string=None): - renew_hook_set = namespace.deploy_hook != namespace.renew_hook - if renew_hook_set and namespace.renew_hook != values: - raise argparse.ArgumentError( - self, "conflicts with --renew-hook value") - namespace.deploy_hook = namespace.renew_hook = values - - -class _RenewHookAction(argparse.Action): - """Action class for parsing renew hooks.""" - - def __call__(self, parser, namespace, values, option_string=None): - deploy_hook_set = namespace.deploy_hook is not None - if deploy_hook_set and namespace.deploy_hook != values: - raise argparse.ArgumentError( - self, "conflicts with --deploy-hook value") - namespace.renew_hook = values - - -def nonnegative_int(value): - """Converts value to an int and checks that it is not negative. - - This function should used as the type parameter for argparse - arguments. - - :param str value: value provided on the command line - - :returns: integer representation of value - :rtype: int - - :raises argparse.ArgumentTypeError: if value isn't a non-negative integer - - """ - try: - int_value = int(value) - except ValueError: - raise argparse.ArgumentTypeError("value must be an integer") - - if int_value < 0: - raise argparse.ArgumentTypeError("value must be non-negative") - return int_value diff --git a/certbot/certbot/_internal/cli/__init__.py b/certbot/certbot/_internal/cli/__init__.py new file mode 100644 index 000000000..96dfb163e --- /dev/null +++ b/certbot/certbot/_internal/cli/__init__.py @@ -0,0 +1,526 @@ +"""Certbot command line argument & config processing.""" +# pylint: disable=too-many-lines +from __future__ import print_function +import logging +import logging.handlers +import argparse +import sys +import certbot._internal.plugins.selection as plugin_selection +from certbot._internal.plugins import disco as plugins_disco + +from acme.magic_typing import Optional + +# pylint: disable=ungrouped-imports +import certbot +from certbot._internal import constants + +import certbot.plugins.enhancements as enhancements + + +from certbot._internal.cli.cli_constants import ( + LEAUTO, + old_path_fragment, + new_path_prefix, + cli_command, + SHORT_USAGE, + COMMAND_OVERVIEW, + HELP_AND_VERSION_USAGE, + ARGPARSE_PARAMS_TO_REMOVE, + EXIT_ACTIONS, + ZERO_ARG_ACTIONS, + VAR_MODIFIERS +) + +from certbot._internal.cli.cli_utils import ( + _Default, + read_file, + flag_default, + config_help, + HelpfulArgumentGroup, + CustomHelpFormatter, + _DomainsAction, + add_domains, + CaseInsensitiveList, + _user_agent_comment_type, + _EncodeReasonAction, + parse_preferred_challenges, + _PrefChallAction, + _DeployHookAction, + _RenewHookAction, + nonnegative_int +) + +# These imports depend on cli_constants and cli_utils. +from certbot._internal.cli.report_config_interaction import report_config_interaction +from certbot._internal.cli.verb_help import VERB_HELP, VERB_HELP_MAP +from certbot._internal.cli.group_adder import _add_all_groups +from certbot._internal.cli.subparsers import _create_subparsers +from certbot._internal.cli.paths_parser import _paths_parser +from certbot._internal.cli.plugins_parsing import _plugins_parsing + +# These imports depend on some or all of the submodules for cli. +from certbot._internal.cli.helpful import HelpfulArgumentParser +# pylint: enable=ungrouped-imports + + +logger = logging.getLogger(__name__) + + +# Global, to save us from a lot of argument passing within the scope of this module +helpful_parser = None # type: Optional[HelpfulArgumentParser] + + +def prepare_and_parse_args(plugins, args, detect_defaults=False): + """Returns parsed command line arguments. + + :param .PluginsRegistry plugins: available plugins + :param list args: command line arguments with the program name removed + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ + + helpful = HelpfulArgumentParser(args, plugins, detect_defaults) + _add_all_groups(helpful) + + # --help is automatically provided by argparse + helpful.add( + None, "-v", "--verbose", dest="verbose_count", action="count", + default=flag_default("verbose_count"), help="This flag can be used " + "multiple times to incrementally increase the verbosity of output, " + "e.g. -vvv.") + helpful.add( + None, "-t", "--text", dest="text_mode", action="store_true", + default=flag_default("text_mode"), help=argparse.SUPPRESS) + helpful.add( + None, "--max-log-backups", type=nonnegative_int, + default=flag_default("max_log_backups"), + help="Specifies the maximum number of backup logs that should " + "be kept by Certbot's built in log rotation. Setting this " + "flag to 0 disables log rotation entirely, causing " + "Certbot to always append to the same log file.") + helpful.add( + [None, "automation", "run", "certonly", "enhance"], + "-n", "--non-interactive", "--noninteractive", + dest="noninteractive_mode", action="store_true", + default=flag_default("noninteractive_mode"), + help="Run without ever asking for user input. This may require " + "additional command line flags; the client will try to explain " + "which ones are required if it finds one missing") + helpful.add( + [None, "register", "run", "certonly", "enhance"], + constants.FORCE_INTERACTIVE_FLAG, action="store_true", + default=flag_default("force_interactive"), + help="Force Certbot to be interactive even if it detects it's not " + "being run in a terminal. This flag cannot be used with the " + "renew subcommand.") + helpful.add( + [None, "run", "certonly", "certificates", "enhance"], + "-d", "--domains", "--domain", dest="domains", + metavar="DOMAIN", action=_DomainsAction, + default=flag_default("domains"), + help="Domain names to apply. For multiple domains you can use " + "multiple -d flags or enter a comma separated list of domains " + "as a parameter. The first domain provided will be the " + "subject CN of the certificate, and all domains will be " + "Subject Alternative Names on the certificate. " + "The first domain will also be used in " + "some software user interfaces and as the file paths for the " + "certificate and related material unless otherwise " + "specified or you already have a certificate with the same " + "name. In the case of a name collision it will append a number " + "like 0001 to the file path name. (default: Ask)") + helpful.add( + [None, "run", "certonly", "register"], + "--eab-kid", dest="eab_kid", + metavar="EAB_KID", + help="Key Identifier for External Account Binding" + ) + helpful.add( + [None, "run", "certonly", "register"], + "--eab-hmac-key", dest="eab_hmac_key", + metavar="EAB_HMAC_KEY", + help="HMAC key for External Account Binding" + ) + helpful.add( + [None, "run", "certonly", "manage", "delete", "certificates", + "renew", "enhance"], "--cert-name", dest="certname", + metavar="CERTNAME", default=flag_default("certname"), + help="Certificate name to apply. This name is used by Certbot for housekeeping " + "and in file paths; it doesn't affect the content of the certificate itself. " + "To see certificate names, run 'certbot certificates'. " + "When creating a new certificate, specifies the new certificate's name. " + "(default: the first provided domain or the name of an existing " + "certificate on your system for the same domains)") + helpful.add( + [None, "testing", "renew", "certonly"], + "--dry-run", action="store_true", dest="dry_run", + default=flag_default("dry_run"), + help="Perform a test run of the client, obtaining test (invalid) certificates" + " but not saving them to disk. This can currently only be used" + " with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run" + " tries to avoid making any persistent changes on a system, it " + " is not completely side-effect free: if used with webserver authenticator plugins" + " like apache and nginx, it makes and then reverts temporary config changes" + " in order to obtain test certificates, and reloads webservers to deploy and then" + " roll back those changes. It also calls --pre-hook and --post-hook commands" + " if they are defined because they may be necessary to accurately simulate" + " renewal. --deploy-hook commands are not called.") + helpful.add( + ["register", "automation"], "--register-unsafely-without-email", action="store_true", + default=flag_default("register_unsafely_without_email"), + help="Specifying this flag enables registering an account with no " + "email address. This is strongly discouraged, because in the " + "event of key loss or account compromise you will irrevocably " + "lose access to your account. You will also be unable to receive " + "notice about impending expiration or revocation of your " + "certificates. Updates to the Subscriber Agreement will still " + "affect you, and will be effective 14 days after posting an " + "update to the web site.") + helpful.add( + ["register", "update_account", "unregister", "automation"], "-m", "--email", + default=flag_default("email"), + help=config_help("email")) + helpful.add(["register", "update_account", "automation"], "--eff-email", action="store_true", + default=flag_default("eff_email"), dest="eff_email", + help="Share your e-mail address with EFF") + helpful.add(["register", "update_account", "automation"], "--no-eff-email", + action="store_false", default=flag_default("eff_email"), dest="eff_email", + help="Don't share your e-mail address with EFF") + helpful.add( + ["automation", "certonly", "run"], + "--keep-until-expiring", "--keep", "--reinstall", + dest="reinstall", action="store_true", default=flag_default("reinstall"), + help="If the requested certificate matches an existing certificate, always keep the " + "existing one until it is due for renewal (for the " + "'run' subcommand this means reinstall the existing certificate). (default: Ask)") + helpful.add( + "automation", "--expand", action="store_true", default=flag_default("expand"), + help="If an existing certificate is a strict subset of the requested names, " + "always expand and replace it with the additional names. (default: Ask)") + helpful.add( + "automation", "--version", action="version", + version="%(prog)s {0}".format(certbot.__version__), + help="show program's version number and exit") + helpful.add( + ["automation", "renew"], + "--force-renewal", "--renew-by-default", dest="renew_by_default", + action="store_true", default=flag_default("renew_by_default"), + help="If a certificate " + "already exists for the requested domains, renew it now, " + "regardless of whether it is near expiry. (Often " + "--keep-until-expiring is more appropriate). Also implies " + "--expand.") + helpful.add( + "automation", "--renew-with-new-domains", dest="renew_with_new_domains", + action="store_true", default=flag_default("renew_with_new_domains"), + help="If a " + "certificate already exists for the requested certificate name " + "but does not match the requested domains, renew it now, " + "regardless of whether it is near expiry.") + helpful.add( + "automation", "--reuse-key", dest="reuse_key", + action="store_true", default=flag_default("reuse_key"), + help="When renewing, use the same private key as the existing " + "certificate.") + + helpful.add( + ["automation", "renew", "certonly"], + "--allow-subset-of-names", action="store_true", + default=flag_default("allow_subset_of_names"), + help="When performing domain validation, do not consider it a failure " + "if authorizations can not be obtained for a strict subset of " + "the requested domains. This may be useful for allowing renewals for " + "multiple domains to succeed even if some domains no longer point " + "at this system. This option cannot be used with --csr.") + helpful.add( + "automation", "--agree-tos", dest="tos", action="store_true", + default=flag_default("tos"), + help="Agree to the ACME Subscriber Agreement (default: Ask)") + helpful.add( + ["unregister", "automation"], "--account", metavar="ACCOUNT_ID", + default=flag_default("account"), + help="Account ID to use") + helpful.add( + "automation", "--duplicate", dest="duplicate", action="store_true", + default=flag_default("duplicate"), + help="Allow making a certificate lineage that duplicates an existing one " + "(both can be renewed in parallel)") + helpful.add( + "automation", "--os-packages-only", action="store_true", + default=flag_default("os_packages_only"), + help="(certbot-auto only) install OS package dependencies and then stop") + helpful.add( + "automation", "--no-self-upgrade", action="store_true", + default=flag_default("no_self_upgrade"), + help="(certbot-auto only) prevent the certbot-auto script from" + " upgrading itself to newer released versions (default: Upgrade" + " automatically)") + helpful.add( + "automation", "--no-bootstrap", action="store_true", + default=flag_default("no_bootstrap"), + help="(certbot-auto only) prevent the certbot-auto script from" + " installing OS-level dependencies (default: Prompt to install " + " OS-wide dependencies, but exit if the user says 'No')") + helpful.add( + "automation", "--no-permissions-check", action="store_true", + default=flag_default("no_permissions_check"), + help="(certbot-auto only) skip the check on the file system" + " permissions of the certbot-auto script") + helpful.add( + ["automation", "renew", "certonly", "run"], + "-q", "--quiet", dest="quiet", action="store_true", + default=flag_default("quiet"), + help="Silence all output except errors. Useful for automation via cron." + " Implies --non-interactive.") + # overwrites server, handled in HelpfulArgumentParser.parse_args() + helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging", + dest="staging", action="store_true", default=flag_default("staging"), + help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent" + " to --server " + constants.STAGING_URI) + helpful.add( + "testing", "--debug", action="store_true", default=flag_default("debug"), + help="Show tracebacks in case of errors, and allow certbot-auto " + "execution on experimental platforms") + helpful.add( + [None, "certonly", "run"], "--debug-challenges", action="store_true", + default=flag_default("debug_challenges"), + help="After setting up challenges, wait for user input before " + "submitting to CA") + helpful.add( + "testing", "--no-verify-ssl", action="store_true", + help=config_help("no_verify_ssl"), + default=flag_default("no_verify_ssl")) + helpful.add( + ["testing", "standalone", "manual"], "--http-01-port", type=int, + dest="http01_port", + default=flag_default("http01_port"), help=config_help("http01_port")) + helpful.add( + ["testing", "standalone"], "--http-01-address", + dest="http01_address", + default=flag_default("http01_address"), help=config_help("http01_address")) + helpful.add( + ["testing", "nginx"], "--https-port", type=int, + default=flag_default("https_port"), + help=config_help("https_port")) + helpful.add( + "testing", "--break-my-certs", action="store_true", + default=flag_default("break_my_certs"), + help="Be willing to replace or renew valid certificates with invalid " + "(testing/staging) certificates") + helpful.add( + "security", "--rsa-key-size", type=int, metavar="N", + default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) + helpful.add( + "security", "--must-staple", action="store_true", + dest="must_staple", default=flag_default("must_staple"), + help=config_help("must_staple")) + helpful.add( + ["security", "enhance"], + "--redirect", action="store_true", dest="redirect", + default=flag_default("redirect"), + help="Automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost. (default: Ask)") + helpful.add( + "security", "--no-redirect", action="store_false", dest="redirect", + default=flag_default("redirect"), + help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost. (default: Ask)") + helpful.add( + ["security", "enhance"], + "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"), + help="Add the Strict-Transport-Security header to every HTTP response." + " Forcing browser to always use SSL for the domain." + " Defends against SSL Stripping.") + helpful.add( + "security", "--no-hsts", action="store_false", dest="hsts", + default=flag_default("hsts"), help=argparse.SUPPRESS) + helpful.add( + ["security", "enhance"], + "--uir", action="store_true", dest="uir", default=flag_default("uir"), + help='Add the "Content-Security-Policy: upgrade-insecure-requests"' + ' header to every HTTP response. Forcing the browser to use' + ' https:// for every http:// resource.') + helpful.add( + "security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"), + help=argparse.SUPPRESS) + helpful.add( + "security", "--staple-ocsp", action="store_true", dest="staple", + default=flag_default("staple"), + help="Enables OCSP Stapling. A valid OCSP response is stapled to" + " the certificate that the server offers during TLS.") + helpful.add( + "security", "--no-staple-ocsp", action="store_false", dest="staple", + default=flag_default("staple"), help=argparse.SUPPRESS) + helpful.add( + "security", "--strict-permissions", action="store_true", + default=flag_default("strict_permissions"), + help="Require that all configuration files are owned by the current " + "user; only needed if your config is somewhere unsafe like /tmp/") + helpful.add( + ["manual", "standalone", "certonly", "renew"], + "--preferred-challenges", dest="pref_challs", + action=_PrefChallAction, default=flag_default("pref_challs"), + help='A sorted, comma delimited list of the preferred challenge to ' + 'use during authorization with the most preferred challenge ' + 'listed first (Eg, "dns" or "http,dns"). ' + 'Not all plugins support all challenges. See ' + 'https://certbot.eff.org/docs/using.html#plugins for details. ' + 'ACME Challenges are versioned, but if you pick "http" rather ' + 'than "http-01", Certbot will select the latest version ' + 'automatically.') + helpful.add( + "renew", "--pre-hook", + help="Command to be run in a shell before obtaining any certificates." + " Intended primarily for renewal, where it can be used to temporarily" + " shut down a webserver that might conflict with the standalone" + " plugin. This will only be called if a certificate is actually to be" + " obtained/renewed. When renewing several certificates that have" + " identical pre-hooks, only the first will be executed.") + helpful.add( + "renew", "--post-hook", + help="Command to be run in a shell after attempting to obtain/renew" + " certificates. Can be used to deploy renewed certificates, or to" + " restart any servers that were stopped by --pre-hook. This is only" + " run if an attempt was made to obtain/renew a certificate. If" + " multiple renewed certificates have identical post-hooks, only" + " one will be run.") + helpful.add("renew", "--renew-hook", + action=_RenewHookAction, help=argparse.SUPPRESS) + helpful.add( + "renew", "--no-random-sleep-on-renew", action="store_false", + default=flag_default("random_sleep_on_renew"), dest="random_sleep_on_renew", + help=argparse.SUPPRESS) + helpful.add( + "renew", "--deploy-hook", action=_DeployHookAction, + help='Command to be run in a shell once for each successfully' + ' issued certificate. For this command, the shell variable' + ' $RENEWED_LINEAGE will point to the config live subdirectory' + ' (for example, "/etc/letsencrypt/live/example.com") containing' + ' the new certificates and keys; the shell variable' + ' $RENEWED_DOMAINS will contain a space-delimited list of' + ' renewed certificate domains (for example, "example.com' + ' www.example.com"') + helpful.add( + "renew", "--disable-hook-validation", + action="store_false", dest="validate_hooks", + default=flag_default("validate_hooks"), + help="Ordinarily the commands specified for" + " --pre-hook/--post-hook/--deploy-hook will be checked for" + " validity, to see if the programs being run are in the $PATH," + " so that mistakes can be caught early, even when the hooks" + " aren't being run just yet. The validation is rather" + " simplistic and fails if you use more advanced shell" + " constructs, so you can use this switch to disable it." + " (default: False)") + helpful.add( + "renew", "--no-directory-hooks", action="store_false", + default=flag_default("directory_hooks"), dest="directory_hooks", + help="Disable running executables found in Certbot's hook directories" + " during renewal. (default: False)") + helpful.add( + "renew", "--disable-renew-updates", action="store_true", + default=flag_default("disable_renew_updates"), dest="disable_renew_updates", + help="Disable automatic updates to your server configuration that" + " would otherwise be done by the selected installer plugin, and triggered" + " when the user executes \"certbot renew\", regardless of if the certificate" + " is renewed. This setting does not apply to important TLS configuration" + " updates.") + helpful.add( + "renew", "--no-autorenew", action="store_false", + default=flag_default("autorenew"), dest="autorenew", + help="Disable auto renewal of certificates.") + + # Populate the command line parameters for new style enhancements + enhancements.populate_cli(helpful.add) + + _create_subparsers(helpful) + _paths_parser(helpful) + # _plugins_parsing should be the last thing to act upon the main + # parser (--help should display plugin-specific options last) + _plugins_parsing(helpful, plugins) + + if not detect_defaults: + global helpful_parser # pylint: disable=global-statement + helpful_parser = helpful + return helpful.parse_args() + + +def set_by_cli(var): + """ + Return True if a particular config variable has been set by the user + (CLI or config file) including if the user explicitly set it to the + default. Returns False if the variable was assigned a default value. + """ + detector = set_by_cli.detector # type: ignore + if detector is None and helpful_parser is not None: + # Setup on first run: `detector` is a weird version of config in which + # the default value of every attribute is wrangled to be boolean-false + plugins = plugins_disco.PluginsRegistry.find_all() + # reconstructed_args == sys.argv[1:], or whatever was passed to main() + reconstructed_args = helpful_parser.args + [helpful_parser.verb] + detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore + plugins, reconstructed_args, detect_defaults=True) + # propagate plugin requests: eg --standalone modifies config.authenticator + detector.authenticator, detector.installer = ( # type: ignore + plugin_selection.cli_plugin_requests(detector)) + + if not isinstance(getattr(detector, var), _Default): + logger.debug("Var %s=%s (set by user).", var, getattr(detector, var)) + return True + + for modifier in VAR_MODIFIERS.get(var, []): + if set_by_cli(modifier): + logger.debug("Var %s=%s (set by user).", + var, VAR_MODIFIERS.get(var, [])) + return True + + return False + + +# static housekeeping var +# functions attributed are not supported by mypy +# https://github.com/python/mypy/issues/2087 +set_by_cli.detector = None # type: ignore + + +def has_default_value(option, value): + """Does option have the default value? + + If the default value of option is not known, False is returned. + + :param str option: configuration variable being considered + :param value: value of the configuration variable named option + + :returns: True if option has the default value, otherwise, False + :rtype: bool + + """ + if helpful_parser is not None: + return (option in helpful_parser.defaults and + helpful_parser.defaults[option] == value) + return False + + +def option_was_set(option, value): + """Was option set by the user or does it differ from the default? + + :param str option: configuration variable being considered + :param value: value of the configuration variable named option + + :returns: True if the option was set, otherwise, False + :rtype: bool + + """ + return set_by_cli(option) or not has_default_value(option, value) + + +def argparse_type(variable): + """Return our argparse type function for a config variable (default: str)""" + # pylint: disable=protected-access + if helpful_parser is not None: + for action in helpful_parser.parser._actions: + if action.type is not None and action.dest == variable: + return action.type + return str diff --git a/certbot/certbot/_internal/cli/cli_constants.py b/certbot/certbot/_internal/cli/cli_constants.py new file mode 100644 index 000000000..748ae0d94 --- /dev/null +++ b/certbot/certbot/_internal/cli/cli_constants.py @@ -0,0 +1,107 @@ +"""Certbot command line constants""" +import sys + +from certbot.compat import os + +# For help strings, figure out how the user ran us. +# When invoked from letsencrypt-auto, sys.argv[0] is something like: +# "/home/user/.local/share/certbot/bin/certbot" +# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before +# running letsencrypt-auto (and sudo stops us from seeing if they did), so it +# should only be used for purposes where inability to detect letsencrypt-auto +# fails safely + +LEAUTO = "letsencrypt-auto" +if "CERTBOT_AUTO" in os.environ: + # if we're here, this is probably going to be certbot-auto, unless the + # user saved the script under a different name + LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"]) + +old_path_fragment = os.path.join(".local", "share", "letsencrypt") +new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt", + "eff.org", "certbot", "venv")) +if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix): + cli_command = LEAUTO +else: + cli_command = "certbot" + + +# Argparse's help formatting has a lot of unhelpful peculiarities, so we want +# to replace as much of it as we can... + +# This is the stub to include in help generated by argparse +SHORT_USAGE = """ + {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... + +Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, +it will attempt to use a webserver both for obtaining and installing the +certificate. """.format(cli_command) + +# This section is used for --help and --help all ; it needs information +# about installed plugins to be fully formatted +COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are: + +obtain, install, and renew certificates: + (default) run Obtain & install a certificate in your current webserver + certonly Obtain or renew a certificate, but do not install it + renew Renew all previously obtained certificates that are near expiry + enhance Add security enhancements to your existing configuration + -d DOMAINS Comma-separated list of domains to obtain a certificate for + + %s + --standalone Run a standalone webserver for authentication + %s + --webroot Place files in a server's webroot folder for authentication + --manual Obtain certificates interactively, or using shell script hooks + + -n Run non-interactively + --test-cert Obtain a test certificate from a staging server + --dry-run Test "renew" or "certonly" without saving any certificates to disk + +manage certificates: + certificates Display information about certificates you have from Certbot + revoke Revoke a certificate (supply --cert-name or --cert-path) + delete Delete a certificate (supply --cert-name) + +manage your account: + register Create an ACME account + unregister Deactivate an ACME account + update_account Update an ACME account + --agree-tos Agree to the ACME server's Subscriber Agreement + -m EMAIL Email address for important account notifications +""" + +# This is the short help for certbot --help, where we disable argparse +# altogether +HELP_AND_VERSION_USAGE = """ +More detailed help: + + -h, --help [TOPIC] print this message, or detailed help on a topic; + the available TOPICS are: + + all, automation, commands, paths, security, testing, or any of the + subcommands or plugins (certonly, renew, install, register, nginx, + apache, standalone, webroot, etc.) + -h all print a detailed help page including all topics + --version print the version number +""" + +# These argparse parameters should be removed when detecting defaults. +ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",) + + +# These sets are used when to help detect options set by the user. +EXIT_ACTIONS = set(("help", "version",)) + + +ZERO_ARG_ACTIONS = set(("store_const", "store_true", + "store_false", "append_const", "count",)) + + +# Maps a config option to a set of config options that may have modified it. +# This dictionary is used recursively, so if A modifies B and B modifies C, +# it is determined that C was modified by the user if A was modified. +VAR_MODIFIERS = {"account": set(("server",)), + "renew_hook": set(("deploy_hook",)), + "server": set(("dry_run", "staging",)), + "webroot_map": set(("webroot_path",))} diff --git a/certbot/certbot/_internal/cli/cli_utils.py b/certbot/certbot/_internal/cli/cli_utils.py new file mode 100644 index 000000000..a0ddce38f --- /dev/null +++ b/certbot/certbot/_internal/cli/cli_utils.py @@ -0,0 +1,239 @@ +"""Certbot command line util function""" +import argparse +import copy + +import zope.interface.interface # pylint: disable=unused-import + +from acme import challenges +from certbot import interfaces +from certbot import util +from certbot import errors +from certbot.compat import os +from certbot._internal import constants + + +class _Default(object): + """A class to use as a default to detect if a value is set by a user""" + + def __bool__(self): + return False + + def __eq__(self, other): + return isinstance(other, _Default) + + def __hash__(self): + return id(_Default) + + def __nonzero__(self): + return self.__bool__() + + +def read_file(filename, mode="rb"): + """Returns the given file's contents. + + :param str filename: path to file + :param str mode: open mode (see `open`) + + :returns: absolute path of filename and its contents + :rtype: tuple + + :raises argparse.ArgumentTypeError: File does not exist or is not readable. + + """ + try: + filename = os.path.abspath(filename) + with open(filename, mode) as the_file: + contents = the_file.read() + return filename, contents + except IOError as exc: + raise argparse.ArgumentTypeError(exc.strerror) + + +def flag_default(name): + """Default value for CLI flag.""" + # XXX: this is an internal housekeeping notion of defaults before + # argparse has been set up; it is not accurate for all flags. Call it + # with caution. Plugin defaults are missing, and some things are using + # defaults defined in this file, not in constants.py :( + return copy.deepcopy(constants.CLI_DEFAULTS[name]) + + +def config_help(name, hidden=False): + """Extract the help message for an `.IConfig` attribute.""" + if hidden: + return argparse.SUPPRESS + field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute + return field.__doc__ + + +class HelpfulArgumentGroup(object): + """Emulates an argparse group for use with HelpfulArgumentParser. + + This class is used in the add_group method of HelpfulArgumentParser. + Command line arguments can be added to the group, but help + suppression and default detection is applied by + HelpfulArgumentParser when necessary. + + """ + def __init__(self, helpful_arg_parser, topic): + self._parser = helpful_arg_parser + self._topic = topic + + def add_argument(self, *args, **kwargs): + """Add a new command line argument to the argument group.""" + self._parser.add(self._topic, *args, **kwargs) + + +class CustomHelpFormatter(argparse.HelpFormatter): + """This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes. + + In particular we fix https://bugs.python.org/issue28742 + """ + + def _get_help_string(self, action): + helpstr = action.help + if '%(default)' not in action.help and '(default:' not in action.help: + if action.default != argparse.SUPPRESS: + defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + helpstr += ' (default: %(default)s)' + return helpstr + + +class _DomainsAction(argparse.Action): + """Action class for parsing domains.""" + + def __call__(self, parser, namespace, domain, option_string=None): + """Just wrap add_domains in argparseese.""" + add_domains(namespace, domain) + + +def add_domains(args_or_config, domains): + """Registers new domains to be used during the current client run. + + Domains are not added to the list of requested domains if they have + already been registered. + + :param args_or_config: parsed command line arguments + :type args_or_config: argparse.Namespace or + configuration.NamespaceConfig + :param str domain: one or more comma separated domains + + :returns: domains after they have been normalized and validated + :rtype: `list` of `str` + + """ + validated_domains = [] + for domain in domains.split(","): + domain = util.enforce_domain_sanity(domain.strip()) + validated_domains.append(domain) + if domain not in args_or_config.domains: + args_or_config.domains.append(domain) + + return validated_domains + + +class CaseInsensitiveList(list): + """A list that will ignore case when searching. + + This class is passed to the `choices` argument of `argparse.add_arguments` + 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()) + + +def _user_agent_comment_type(value): + if "(" in value or ")" in value: + raise argparse.ArgumentTypeError("may not contain parentheses") + return value + + +class _EncodeReasonAction(argparse.Action): + """Action class for parsing revocation reason.""" + + def __call__(self, parser, namespace, reason, option_string=None): + """Encodes the reason for certificate revocation.""" + code = constants.REVOCATION_REASONS[reason.lower()] + setattr(namespace, self.dest, code) + + +def parse_preferred_challenges(pref_challs): + """Translate and validate preferred challenges. + + :param pref_challs: list of preferred challenge types + :type pref_challs: `list` of `str` + + :returns: validated list of preferred challenge types + :rtype: `list` of `str` + + :raises errors.Error: if pref_challs is invalid + + """ + aliases = {"dns": "dns-01", "http": "http-01"} + challs = [c.strip() for c in pref_challs] + challs = [aliases.get(c, c) for c in challs] + + unrecognized = ", ".join(name for name in challs + if name not in challenges.Challenge.TYPES) + if unrecognized: + raise errors.Error( + "Unrecognized challenges: {0}".format(unrecognized)) + return challs + + +class _PrefChallAction(argparse.Action): + """Action class for parsing preferred challenges.""" + + def __call__(self, parser, namespace, pref_challs, option_string=None): + try: + challs = parse_preferred_challenges(pref_challs.split(",")) + except errors.Error as error: + raise argparse.ArgumentError(self, str(error)) + namespace.pref_challs.extend(challs) + + +class _DeployHookAction(argparse.Action): + """Action class for parsing deploy hooks.""" + + def __call__(self, parser, namespace, values, option_string=None): + renew_hook_set = namespace.deploy_hook != namespace.renew_hook + if renew_hook_set and namespace.renew_hook != values: + raise argparse.ArgumentError( + self, "conflicts with --renew-hook value") + namespace.deploy_hook = namespace.renew_hook = values + + +class _RenewHookAction(argparse.Action): + """Action class for parsing renew hooks.""" + + def __call__(self, parser, namespace, values, option_string=None): + deploy_hook_set = namespace.deploy_hook is not None + if deploy_hook_set and namespace.deploy_hook != values: + raise argparse.ArgumentError( + self, "conflicts with --deploy-hook value") + namespace.renew_hook = values + + +def nonnegative_int(value): + """Converts value to an int and checks that it is not negative. + + This function should used as the type parameter for argparse + arguments. + + :param str value: value provided on the command line + + :returns: integer representation of value + :rtype: int + + :raises argparse.ArgumentTypeError: if value isn't a non-negative integer + + """ + try: + int_value = int(value) + except ValueError: + raise argparse.ArgumentTypeError("value must be an integer") + + if int_value < 0: + raise argparse.ArgumentTypeError("value must be non-negative") + return int_value diff --git a/certbot/certbot/_internal/cli/group_adder.py b/certbot/certbot/_internal/cli/group_adder.py new file mode 100644 index 000000000..f22fbc496 --- /dev/null +++ b/certbot/certbot/_internal/cli/group_adder.py @@ -0,0 +1,19 @@ +"""This module contains a function to add the groups of arguments for the help +display""" +from certbot._internal.cli import VERB_HELP + + +def _add_all_groups(helpful): + helpful.add_group("automation", description="Flags for automating execution & other tweaks") + helpful.add_group("security", description="Security parameters & server settings") + helpful.add_group("testing", + description="The following flags are meant for testing and integration purposes only.") + helpful.add_group("paths", description="Flags for changing execution paths & servers") + helpful.add_group("manage", + description="Various subcommands and flags are available for managing your certificates:", + verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) + + # VERBS + for verb, docs in VERB_HELP: + name = docs.get("realname", verb) + helpful.add_group(name, description=docs["opts"]) diff --git a/certbot/certbot/_internal/cli/helpful.py b/certbot/certbot/_internal/cli/helpful.py new file mode 100644 index 000000000..e63ab4b87 --- /dev/null +++ b/certbot/certbot/_internal/cli/helpful.py @@ -0,0 +1,468 @@ +"""Certbot command line argument parser""" +from __future__ import print_function +import argparse +import copy +import glob +import sys +import configargparse +import six +import zope.component +import zope.interface + +from zope.interface import interfaces as zope_interfaces + +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any, Dict, Optional +# pylint: enable=unused-import, no-name-in-module + +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os +from certbot._internal import constants +from certbot._internal import hooks + +from certbot.display import util as display_util + +from certbot._internal.cli import ( + SHORT_USAGE, + CustomHelpFormatter, + flag_default, + VERB_HELP, + VERB_HELP_MAP, + COMMAND_OVERVIEW, + HELP_AND_VERSION_USAGE, + _Default, + add_domains, + EXIT_ACTIONS, + ZERO_ARG_ACTIONS, + ARGPARSE_PARAMS_TO_REMOVE, + HelpfulArgumentGroup +) + + +class HelpfulArgumentParser(object): + """Argparse Wrapper. + + This class wraps argparse, adding the ability to make --help less + verbose, and request help on specific subcategories at a time, eg + 'certbot --help security' for security options. + + """ + def __init__(self, args, plugins, detect_defaults=False): + from certbot._internal import main + self.VERBS = { + "auth": main.certonly, + "certonly": main.certonly, + "run": main.run, + "install": main.install, + "plugins": main.plugins_cmd, + "register": main.register, + "update_account": main.update_account, + "unregister": main.unregister, + "renew": main.renew, + "revoke": main.revoke, + "rollback": main.rollback, + "everything": main.run, + "update_symlinks": main.update_symlinks, + "certificates": main.certificates, + "delete": main.delete, + "enhance": main.enhance, + } + + # Get notification function for printing + try: + self.notify = zope.component.getUtility( + interfaces.IDisplay).notification + except zope_interfaces.ComponentLookupError: + self.notify = display_util.NoninteractiveDisplay( + sys.stdout).notification + + + # List of topics for which additional help can be provided + HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] + HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] + + plugin_names = list(plugins) + self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore + + self.detect_defaults = detect_defaults + self.args = args + + if self.args and self.args[0] == 'help': + self.args[0] = '--help' + + self.determine_verb() + help1 = self.prescan_for_flag("-h", self.help_topics) + help2 = self.prescan_for_flag("--help", self.help_topics) + if isinstance(help1, bool) and isinstance(help2, bool): + self.help_arg = help1 or help2 + else: + self.help_arg = help1 if isinstance(help1, six.string_types) else help2 + + short_usage = self._usage_string(plugins, self.help_arg) + + self.visible_topics = self.determine_help_topics(self.help_arg) + + # elements are added by .add_group() + self.groups = {} # type: Dict[str, argparse._ArgumentGroup] + # elements are added by .parse_args() + self.defaults = {} # type: Dict[str, Any] + + self.parser = configargparse.ArgParser( + prog="certbot", + usage=short_usage, + formatter_class=CustomHelpFormatter, + args_for_setting_config_path=["-c", "--config"], + default_config_files=flag_default("config_files"), + config_arg_help_message="path to config file (default: {0})".format( + " and ".join(flag_default("config_files")))) + + # This is the only way to turn off overly verbose config flag documentation + self.parser._add_config_file_help = False + + # Help that are synonyms for --help subcommands + COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] + + def _list_subcommands(self): + longest = max(len(v) for v in VERB_HELP_MAP) + + text = "The full list of available SUBCOMMANDS is:\n\n" + for verb, props in sorted(VERB_HELP): + doc = props.get("short", "") + text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest) + + text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n" + return text + + def _usage_string(self, plugins, help_arg): + """Make usage strings late so that plugins can be initialised late + + :param plugins: all discovered plugins + :param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC + :rtype: str + :returns: a short usage string for the top of --help TOPIC) + """ + if "nginx" in plugins: + nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" + else: + nginx_doc = "(the certbot nginx plugin is not installed)" + if "apache" in plugins: + apache_doc = "--apache Use the Apache plugin for authentication & installation" + else: + apache_doc = "(the certbot apache plugin is not installed)" + + usage = SHORT_USAGE + if help_arg is True: + self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) + sys.exit(0) + elif help_arg in self.COMMANDS_TOPICS: + self.notify(usage + self._list_subcommands()) + sys.exit(0) + elif help_arg == "all": + # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at + # the top; if we're doing --help someothertopic, it's OT so it's not + usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc) + else: + custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None) + usage = custom if custom else usage + + return usage + + def remove_config_file_domains_for_renewal(self, parsed_args): + """Make "certbot renew" safe if domains are set in cli.ini.""" + # Works around https://github.com/certbot/certbot/issues/4096 + if self.verb == "renew": + for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access + if source.startswith("config_file") and "domains" in flags: + parsed_args.domains = _Default() if self.detect_defaults else [] + + def parse_args(self): + """Parses command line arguments and returns the result. + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ + parsed_args = self.parser.parse_args(self.args) + parsed_args.func = self.VERBS[self.verb] + parsed_args.verb = self.verb + + self.remove_config_file_domains_for_renewal(parsed_args) + + if self.detect_defaults: + return parsed_args + + self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key))) + for key in vars(parsed_args)) + + # Do any post-parsing homework here + + if self.verb == "renew": + if parsed_args.force_interactive: + raise errors.Error( + "{0} cannot be used with renew".format( + constants.FORCE_INTERACTIVE_FLAG)) + parsed_args.noninteractive_mode = True + + if parsed_args.force_interactive and parsed_args.noninteractive_mode: + raise errors.Error( + "Flag for non-interactive mode and {0} conflict".format( + constants.FORCE_INTERACTIVE_FLAG)) + + if parsed_args.staging or parsed_args.dry_run: + self.set_test_server(parsed_args) + + if parsed_args.csr: + self.handle_csr(parsed_args) + + if parsed_args.must_staple: + parsed_args.staple = True + + if parsed_args.validate_hooks: + hooks.validate_hooks(parsed_args) + + if parsed_args.allow_subset_of_names: + if any(util.is_wildcard_domain(d) for d in parsed_args.domains): + raise errors.Error("Using --allow-subset-of-names with a" + " wildcard domain is not supported.") + + if parsed_args.hsts and parsed_args.auto_hsts: + raise errors.Error( + "Parameters --hsts and --auto-hsts cannot be used simultaneously.") + + return parsed_args + + def set_test_server(self, parsed_args): + """We have --staging/--dry-run; perform sanity check and set config.server""" + + # Flag combinations should produce these results: + # | --staging | --dry-run | + # ------------------------------------------------------------ + # | --server acme-v02 | Use staging | Use staging | + # | --server acme-staging-v02 | Use staging | Use staging | + # | --server | Conflict error | Use | + + default_servers = (flag_default("server"), constants.STAGING_URI) + + if parsed_args.staging and parsed_args.server not in default_servers: + raise errors.Error("--server value conflicts with --staging") + + if parsed_args.server in default_servers: + parsed_args.server = constants.STAGING_URI + + if parsed_args.dry_run: + if self.verb not in ["certonly", "renew"]: + raise errors.Error("--dry-run currently only works with the " + "'certonly' or 'renew' subcommands (%r)" % self.verb) + parsed_args.break_my_certs = parsed_args.staging = True + if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")): + # The user has a prod account, but might not have a staging + # one; we don't want to start trying to perform interactive registration + parsed_args.tos = True + parsed_args.register_unsafely_without_email = True + + def handle_csr(self, parsed_args): + """Process a --csr flag.""" + if parsed_args.verb != "certonly": + raise errors.Error("Currently, a CSR file may only be specified " + "when obtaining a new or replacement " + "via the certonly command. Please try the " + "certonly command instead.") + if parsed_args.allow_subset_of_names: + raise errors.Error("--allow-subset-of-names cannot be used with --csr") + + csrfile, contents = parsed_args.csr[0:2] + typ, csr, domains = crypto_util.import_csr_file(csrfile, contents) + + # This is not necessary for webroot to work, however, + # obtain_certificate_from_csr requires parsed_args.domains to be set + for domain in domains: + add_domains(parsed_args, domain) + + if not domains: + # TODO: add CN to domains instead: + raise errors.Error( + "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" + % parsed_args.csr[0]) + + parsed_args.actual_csr = (csr, typ) + + csr_domains = {d.lower() for d in domains} + config_domains = set(parsed_args.domains) + if csr_domains != config_domains: + raise errors.ConfigurationError( + "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" + .format(", ".join(csr_domains), ", ".join(config_domains))) + + + def determine_verb(self): + """Determines the verb/subcommand provided by the user. + + This function works around some of the limitations of argparse. + + """ + if "-h" in self.args or "--help" in self.args: + # all verbs double as help arguments; don't get them confused + self.verb = "help" + return + + for i, token in enumerate(self.args): + if token in self.VERBS: + verb = token + if verb == "auth": + verb = "certonly" + if verb == "everything": + verb = "run" + self.verb = verb + self.args.pop(i) + return + + self.verb = "run" + + def prescan_for_flag(self, flag, possible_arguments): + """Checks cli input for flags. + + Check for a flag, which accepts a fixed set of possible arguments, in + the command line; we will use this information to configure argparse's + help correctly. Return the flag's argument, if it has one that matches + the sequence @possible_arguments; otherwise return whether the flag is + present. + + """ + if flag not in self.args: + return False + pos = self.args.index(flag) + try: + nxt = self.args[pos + 1] + if nxt in possible_arguments: + return nxt + except IndexError: + pass + return True + + def add(self, topics, *args, **kwargs): + """Add a new command line argument. + + :param topics: str or [str] help topic(s) this should be listed under, + or None for options that don't fit under a specific + topic which will only be shown in "--help all" output. + The first entry determines where the flag lives in the + "--help all" output (None -> "optional arguments"). + :param list *args: the names of this argument flag + :param dict **kwargs: various argparse settings for this argument + + """ + + if isinstance(topics, list): + # if this flag can be listed in multiple sections, try to pick the one + # that the user has asked for help about + topic = self.help_arg if self.help_arg in topics else topics[0] + else: + topic = topics # there's only one + + if self.detect_defaults: + kwargs = self.modify_kwargs_for_default_detection(**kwargs) + + if self.visible_topics[topic]: + if topic in self.groups: + group = self.groups[topic] + group.add_argument(*args, **kwargs) + else: + self.parser.add_argument(*args, **kwargs) + else: + kwargs["help"] = argparse.SUPPRESS + self.parser.add_argument(*args, **kwargs) + + def modify_kwargs_for_default_detection(self, **kwargs): + """Modify an arg so we can check if it was set by the user. + + Changes the parameters given to argparse when adding an argument + so we can properly detect if the value was set by the user. + + :param dict kwargs: various argparse settings for this argument + + :returns: a modified versions of kwargs + :rtype: dict + + """ + action = kwargs.get("action", None) + if action not in EXIT_ACTIONS: + kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else + "store") + kwargs["default"] = _Default() + for param in ARGPARSE_PARAMS_TO_REMOVE: + kwargs.pop(param, None) + + return kwargs + + def add_deprecated_argument(self, argument_name, num_args): + """Adds a deprecated argument with the name argument_name. + + Deprecated arguments are not shown in the help. If they are used + on the command line, a warning is shown stating that the + argument is deprecated and no other action is taken. + + :param str argument_name: Name of deprecated argument. + :param int nargs: Number of arguments the option takes. + + """ + util.add_deprecated_argument( + self.parser.add_argument, argument_name, num_args) + + def add_group(self, topic, verbs=(), **kwargs): + """Create a new argument group. + + This method must be called once for every topic, however, calls + to this function are left next to the argument definitions for + clarity. + + :param str topic: Name of the new argument group. + :param str verbs: List of subcommands that should be documented as part of + this help group / topic + + :returns: The new argument group. + :rtype: `HelpfulArgumentGroup` + + """ + if self.visible_topics[topic]: + self.groups[topic] = self.parser.add_argument_group(topic, **kwargs) + if self.help_arg: + for v in verbs: + self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) + return HelpfulArgumentGroup(self, topic) + + def add_plugin_args(self, plugins): + """ + + Let each of the plugins add its own command line arguments, which + may or may not be displayed as help topics. + + """ + for name, plugin_ep in six.iteritems(plugins): + parser_or_group = self.add_group(name, + description=plugin_ep.long_description) + plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) + + def determine_help_topics(self, chosen_topic): + """ + + The user may have requested help on a topic, return a dict of which + topics to display. @chosen_topic has prescan_for_flag's return type + + :returns: dict + + """ + # topics maps each topic to whether it should be documented by + # argparse on the command line + if chosen_topic == "auth": + chosen_topic = "certonly" + if chosen_topic == "everything": + chosen_topic = "run" + if chosen_topic == "all": + # Addition of condition closes #6209 (removal of duplicate route53 option). + return {t: t != 'certbot-route53:auth' for t in self.help_topics} + elif not chosen_topic: + return {t: False for t in self.help_topics} + return {t: t == chosen_topic for t in self.help_topics} diff --git a/certbot/certbot/_internal/cli/paths_parser.py b/certbot/certbot/_internal/cli/paths_parser.py new file mode 100644 index 000000000..4378435d7 --- /dev/null +++ b/certbot/certbot/_internal/cli/paths_parser.py @@ -0,0 +1,50 @@ +"""This is a module that adds configuration to the argument parser regarding +paths for certificates""" +from certbot.compat import os +from certbot._internal.cli import ( + read_file, + flag_default, + config_help +) + + +def _paths_parser(helpful): + add = helpful.add + verb = helpful.verb + if verb == "help": + verb = helpful.help_arg + + cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." + sections = ["paths", "install", "revoke", "certonly", "manage"] + if verb == "certonly": + add(sections, "--cert-path", type=os.path.abspath, + default=flag_default("auth_cert_path"), help=cph) + elif verb == "revoke": + add(sections, "--cert-path", type=read_file, required=False, help=cph) + else: + add(sections, "--cert-path", type=os.path.abspath, help=cph) + + section = "paths" + if verb in ("install", "revoke"): + section = verb + # revoke --key-path reads a file, install --key-path takes a string + add(section, "--key-path", + type=((verb == "revoke" and read_file) or os.path.abspath), + help="Path to private key for certificate installation " + "or revocation (if account key is missing)") + + default_cp = None + if verb == "certonly": + default_cp = flag_default("auth_chain_path") + add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath, + help="Accompanying path to a full certificate chain (certificate plus chain).") + add("paths", "--chain-path", default=default_cp, type=os.path.abspath, + help="Accompanying path to a certificate chain.") + add("paths", "--config-dir", default=flag_default("config_dir"), + help=config_help("config_dir")) + add("paths", "--work-dir", default=flag_default("work_dir"), + help=config_help("work_dir")) + add("paths", "--logs-dir", default=flag_default("logs_dir"), + help="Logs directory.") + add("paths", "--server", default=flag_default("server"), + help=config_help("server")) diff --git a/certbot/certbot/_internal/cli/plugins_parsing.py b/certbot/certbot/_internal/cli/plugins_parsing.py new file mode 100644 index 000000000..9e11ad3ab --- /dev/null +++ b/certbot/certbot/_internal/cli/plugins_parsing.py @@ -0,0 +1,97 @@ +"""This is a module that handles parsing of plugins for the argument parser""" +from certbot._internal.cli import flag_default + + +def _plugins_parsing(helpful, plugins): + # It's nuts, but there are two "plugins" topics. Somehow this works + helpful.add_group( + "plugins", description="Plugin Selection: Certbot client supports an " + "extensible plugins architecture. See '%(prog)s plugins' for a " + "list of all installed plugins and their names. You can force " + "a particular plugin by setting options provided below. Running " + "--help will list flags specific to that plugin.") + + helpful.add("plugins", "--configurator", default=flag_default("configurator"), + help="Name of the plugin that is both an authenticator and an installer." + " Should not be used together with --authenticator or --installer. " + "(default: Ask)") + helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), + help="Authenticator plugin name.") + helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), + help="Installer plugin name (also used to find domains).") + helpful.add(["plugins", "certonly", "run", "install"], + "--apache", action="store_true", default=flag_default("apache"), + help="Obtain and install certificates using Apache") + helpful.add(["plugins", "certonly", "run", "install"], + "--nginx", action="store_true", default=flag_default("nginx"), + help="Obtain and install certificates using Nginx") + helpful.add(["plugins", "certonly"], "--standalone", action="store_true", + default=flag_default("standalone"), + help='Obtain certificates using a "standalone" webserver.') + helpful.add(["plugins", "certonly"], "--manual", action="store_true", + default=flag_default("manual"), + help="Provide laborious manual instructions for obtaining a certificate") + helpful.add(["plugins", "certonly"], "--webroot", action="store_true", + default=flag_default("webroot"), + help="Obtain certificates by placing files in a webroot directory.") + helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", + default=flag_default("dns_cloudflare"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Cloudflare for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true", + default=flag_default("dns_cloudxns"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using CloudXNS for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true", + default=flag_default("dns_digitalocean"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DigitalOcean for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true", + default=flag_default("dns_dnsimple"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DNSimple for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", + default=flag_default("dns_dnsmadeeasy"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DNS Made Easy for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", + default=flag_default("dns_gehirn"), + help=("Obtain certificates using a DNS TXT record " + "(if you are using Gehirn Infrastructure Service for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", + default=flag_default("dns_google"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Google Cloud DNS).")) + helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true", + default=flag_default("dns_linode"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Linode for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true", + default=flag_default("dns_luadns"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using LuaDNS for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", + default=flag_default("dns_nsone"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using NS1 for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true", + default=flag_default("dns_ovh"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using OVH for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true", + default=flag_default("dns_rfc2136"), + help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).") + helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true", + default=flag_default("dns_route53"), + help=("Obtain certificates using a DNS TXT record (if you are using Route53 for " + "DNS).")) + helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true", + default=flag_default("dns_sakuracloud"), + help=("Obtain certificates using a DNS TXT record " + "(if you are using Sakura Cloud for DNS).")) + + # things should not be reorder past/pre this comment: + # plugins_group should be displayed in --help before plugin + # specific groups (so that plugins_group.description makes sense) + + helpful.add_plugin_args(plugins) diff --git a/certbot/certbot/_internal/cli/report_config_interaction.py b/certbot/certbot/_internal/cli/report_config_interaction.py new file mode 100644 index 000000000..39266e776 --- /dev/null +++ b/certbot/certbot/_internal/cli/report_config_interaction.py @@ -0,0 +1,27 @@ +"""This is a module that reports config option interaction that should be +checked by set_by_cli""" +import six + +from certbot._internal.cli import VAR_MODIFIERS + + +def report_config_interaction(modified, modifiers): + """Registers config option interaction to be checked by set_by_cli. + + This function can be called by during the __init__ or + add_parser_arguments methods of plugins to register interactions + between config options. + + :param modified: config options that can be modified by modifiers + :type modified: iterable or str (string_types) + :param modifiers: config options that modify modified + :type modifiers: iterable or str (string_types) + + """ + if isinstance(modified, six.string_types): + modified = (modified,) + if isinstance(modifiers, six.string_types): + modifiers = (modifiers,) + + for var in modified: + VAR_MODIFIERS.setdefault(var, set()).update(modifiers) diff --git a/certbot/certbot/_internal/cli/subparsers.py b/certbot/certbot/_internal/cli/subparsers.py new file mode 100644 index 000000000..13f8705ce --- /dev/null +++ b/certbot/certbot/_internal/cli/subparsers.py @@ -0,0 +1,72 @@ +"""This module creates subparsers for the argument parser""" +from certbot import interfaces +from certbot._internal import constants + +from certbot._internal.cli import ( + flag_default, + read_file, + CaseInsensitiveList, + _user_agent_comment_type, + _EncodeReasonAction +) + + +def _create_subparsers(helpful): + from certbot._internal.client import sample_user_agent # avoid import loops + helpful.add( + None, "--user-agent", default=flag_default("user_agent"), + help='Set a custom user agent string for the client. User agent strings allow ' + 'the CA to collect high level statistics about success rates by OS, ' + 'plugin and use 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: {0}). The flags encoded in the user agent are: ' + '--duplicate, --force-renew, --allow-subset-of-names, -n, and ' + 'whether any hooks are set.'.format(sample_user_agent())) + helpful.add( + None, "--user-agent-comment", default=flag_default("user_agent_comment"), + type=_user_agent_comment_type, + help="Add a comment to the default user agent string. May be used when repackaging Certbot " + "or calling it from another tool to allow additional statistical data to be collected." + " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") + helpful.add("certonly", + "--csr", default=flag_default("csr"), type=read_file, + help="Path to a Certificate Signing Request (CSR) in DER or PEM format." + " 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)), + action=_EncodeReasonAction, default=flag_default("reason"), + help="Specify reason for revoking certificate. (default: unspecified)") + helpful.add("revoke", + "--delete-after-revoke", action="store_true", + default=flag_default("delete_after_revoke"), + help="Delete certificates after revoking them, along with all previous and later " + "versions of those certificates.") + helpful.add("revoke", + "--no-delete-after-revoke", action="store_false", + dest="delete_after_revoke", + default=flag_default("delete_after_revoke"), + help="Do not delete certificates after revoking them. This " + "option should be used with caution because the 'renew' " + "subcommand will attempt to renew undeleted revoked " + "certificates.") + helpful.add("rollback", + "--checkpoints", type=int, metavar="N", + default=flag_default("rollback_checkpoints"), + help="Revert configuration N number of checkpoints.") + helpful.add("plugins", + "--init", action="store_true", default=flag_default("init"), + help="Initialize plugins.") + helpful.add("plugins", + "--prepare", action="store_true", default=flag_default("prepare"), + help="Initialize and prepare plugins.") + helpful.add("plugins", + "--authenticators", action="append_const", dest="ifaces", + default=flag_default("ifaces"), + const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.") + helpful.add("plugins", + "--installers", action="append_const", dest="ifaces", + default=flag_default("ifaces"), + const=interfaces.IInstaller, help="Limit to installer plugins only.") diff --git a/certbot/certbot/_internal/cli/verb_help.py b/certbot/certbot/_internal/cli/verb_help.py new file mode 100644 index 000000000..131cfec96 --- /dev/null +++ b/certbot/certbot/_internal/cli/verb_help.py @@ -0,0 +1,106 @@ +"""This module contain help information for verbs supported by certbot""" +from certbot.compat import os +from certbot._internal.cli import ( + SHORT_USAGE, + flag_default +) + +# The attributes here are: +# short: a string that will be displayed by "certbot -h commands" +# opts: a string that heads the section of flags with which this command is documented, +# both for "certbot -h SUBCOMMAND" and "certbot -h all" +# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND" +VERB_HELP = [ + ("run (default)", { + "short": "Obtain/renew a certificate, and install it", + "opts": "Options for obtaining & installing certificates", + "usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""), + "realname": "run" + }), + ("certonly", { + "short": "Obtain or renew a certificate, but do not install it", + "opts": "Options for modifying how a certificate is obtained", + "usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n" + "This command obtains a TLS/SSL certificate without installing it anywhere.") + }), + ("renew", { + "short": "Renew all certificates (or one specified with --cert-name)", + "opts": ("The 'renew' subcommand will attempt to renew all" + " certificates (or more precisely, certificate lineages) you have" + " previously obtained if they are close to expiry, and print a" + " summary of the results. By default, 'renew' will reuse the options" + " used to create obtain or most recently successfully renew each" + " certificate lineage. You can try it with `--dry-run` first. For" + " more fine-grained control, you can renew individual lineages with" + " the `certonly` subcommand. Hooks are available to run commands" + " before and after renewal; see" + " https://certbot.eff.org/docs/using.html#renewal for more" + " information on these."), + "usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n" + }), + ("certificates", { + "short": "List certificates managed by Certbot", + "opts": "List certificates managed by Certbot", + "usage": ("\n\n certbot certificates [options] ...\n\n" + "Print information about the status of certificates managed by Certbot.") + }), + ("delete", { + "short": "Clean up all files related to a certificate", + "opts": "Options for deleting a certificate", + "usage": "\n\n certbot delete --cert-name CERTNAME\n\n" + }), + ("revoke", { + "short": "Revoke a certificate specified with --cert-path or --cert-name", + "opts": "Options for revocation of certificates", + "usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | " + "--cert-name example.com] [options]\n\n" + }), + ("register", { + "short": "Register for account with Let's Encrypt / other ACME server", + "opts": "Options for account registration", + "usage": "\n\n certbot register --email user@example.com [options]\n\n" + }), + ("update_account", { + "short": "Update existing account with Let's Encrypt / other ACME server", + "opts": "Options for account modification", + "usage": "\n\n certbot update_account --email updated_email@example.com [options]\n\n" + }), + ("unregister", { + "short": "Irrevocably deactivate your account", + "opts": "Options for account deactivation.", + "usage": "\n\n certbot unregister [options]\n\n" + }), + ("install", { + "short": "Install an arbitrary certificate in a server", + "opts": "Options for modifying how a certificate is deployed", + "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " + " --key-path /path/to/private-key [options]\n\n" + }), + ("rollback", { + "short": "Roll back server conf changes made during certificate installation", + "opts": "Options for rolling back server configuration changes", + "usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n" + }), + ("plugins", { + "short": "List plugins that are installed and available on your system", + "opts": 'Options for the "plugins" subcommand', + "usage": "\n\n certbot plugins [options]\n\n" + }), + ("update_symlinks", { + "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", + "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " + "or edited a renewal configuration file".format( + os.path.join(flag_default("config_dir"), "live"))), + "usage": "\n\n certbot update_symlinks [options]\n\n" + }), + ("enhance", { + "short": "Add security enhancements to your existing configuration", + "opts": ("Helps to harden the TLS configuration by adding security enhancements " + "to already existing configuration."), + "usage": "\n\n certbot enhance [options]\n\n" + }), +] + + +# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful +VERB_HELP_MAP = dict(VERB_HELP) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 05da1da4e..3a7fb57f8 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -93,7 +93,7 @@ class ParseTest(unittest.TestCase): return output.getvalue() - @mock.patch("certbot._internal.cli.flag_default") + @mock.patch("certbot._internal.cli.helpful.flag_default") def test_cli_ini_domains(self, mock_flag_default): with tempfile.NamedTemporaryFile() as tmp_config: tmp_config.close() # close now because of compatibility issues on Windows diff --git a/certbot/tests/helpful_test.py b/certbot/tests/helpful_test.py new file mode 100644 index 000000000..292e55304 --- /dev/null +++ b/certbot/tests/helpful_test.py @@ -0,0 +1,193 @@ +"""Tests for certbot.helpful_parser""" +import unittest + +from certbot import errors +from certbot._internal.cli import HelpfulArgumentParser +from certbot._internal.cli import _DomainsAction +from certbot._internal import constants + + +class TestScanningFlags(unittest.TestCase): + '''Test the prescan_for_flag method of HelpfulArgumentParser''' + def test_prescan_no_help_flag(self): + arg_parser = HelpfulArgumentParser(['run'], {}) + detected_flag = arg_parser.prescan_for_flag('--help', + ['all', 'certonly']) + self.assertFalse(detected_flag) + detected_flag = arg_parser.prescan_for_flag('-h', + ['all, certonly']) + self.assertFalse(detected_flag) + + def test_prescan_unvalid_topic(self): + arg_parser = HelpfulArgumentParser(['--help', 'all'], {}) + detected_flag = arg_parser.prescan_for_flag('--help', + ['potato']) + self.assertIs(detected_flag, True) + detected_flag = arg_parser.prescan_for_flag('-h', + arg_parser.help_topics) + self.assertFalse(detected_flag) + + def test_prescan_valid_topic(self): + arg_parser = HelpfulArgumentParser(['-h', 'all'], {}) + detected_flag = arg_parser.prescan_for_flag('-h', + arg_parser.help_topics) + self.assertEqual(detected_flag, 'all') + detected_flag = arg_parser.prescan_for_flag('--help', + arg_parser.help_topics) + self.assertFalse(detected_flag) + +class TestDetermineVerbs(unittest.TestCase): + '''Tests for determine_verb methods of HelpfulArgumentParser''' + def test_determine_verb_wrong_verb(self): + arg_parser = HelpfulArgumentParser(['potato'], {}) + self.assertEqual(arg_parser.verb, "run") + self.assertEqual(arg_parser.args, ["potato"]) + + def test_determine_verb_help(self): + arg_parser = HelpfulArgumentParser(['--help', 'everything'], {}) + self.assertEqual(arg_parser.verb, "help") + self.assertEqual(arg_parser.args, ["--help", "everything"]) + arg_parser = HelpfulArgumentParser(['-d', 'some_domain', '--help', + 'all'], {}) + self.assertEqual(arg_parser.verb, "help") + self.assertEqual(arg_parser.args, ['-d', 'some_domain', '--help', + 'all']) + + def test_determine_verb(self): + arg_parser = HelpfulArgumentParser(['certonly'], {}) + self.assertEqual(arg_parser.verb, 'certonly') + self.assertEqual(arg_parser.args, []) + + arg_parser = HelpfulArgumentParser(['auth'], {}) + self.assertEqual(arg_parser.verb, 'certonly') + self.assertEqual(arg_parser.args, []) + + arg_parser = HelpfulArgumentParser(['everything'], {}) + self.assertEqual(arg_parser.verb, 'run') + self.assertEqual(arg_parser.args, []) + + +class TestAdd(unittest.TestCase): + '''Tests for add method in HelpfulArgumentParser''' + def test_add_trivial_argument(self): + arg_parser = HelpfulArgumentParser(['run'], {}) + arg_parser.add(None, "--hello-world") + parsed_args = arg_parser.parser.parse_args(['--hello-world', + 'Hello World!']) + self.assertIs(parsed_args.hello_world, 'Hello World!') + self.assertFalse(hasattr(parsed_args, 'potato')) + + def test_add_expected_argument(self): + arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) + arg_parser.add( + [None, "run", "certonly", "register"], + "--eab-kid", dest="eab_kid", action="store", + metavar="EAB_KID", + help="Key Identifier for External Account Binding") + parsed_args = arg_parser.parser.parse_args(["--eab-kid", None]) + self.assertIs(parsed_args.eab_kid, None) + self.assertTrue(hasattr(parsed_args, 'eab_kid')) + + +class TestAddGroup(unittest.TestCase): + '''Test add_group method of HelpfulArgumentParser''' + def test_add_group_no_input(self): + arg_parser = HelpfulArgumentParser(['run'], {}) + self.assertRaises(TypeError, arg_parser.add_group) + + def test_add_group_topic_not_visible(self): + # The user request help on run. A topic that given somewhere in the + # args won't be added to the groups in the parser. + arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) + arg_parser.add_group("auth", + description="description of auth") + self.assertEqual(arg_parser.groups, {}) + + def test_add_group_topic_requested_help(self): + arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) + arg_parser.add_group("run", + description="description of run") + self.assertTrue(arg_parser.groups["run"]) + arg_parser.add_group("certonly", description="description of certonly") + with self.assertRaises(KeyError): + self.assertFalse(arg_parser.groups["certonly"]) + + +class TestParseArgsErrors(unittest.TestCase): + '''Tests for errors that should be met for some cases in parse_args method + in HelpfulArgumentParser''' + def test_parse_args_renew_force_interactive(self): + arg_parser = HelpfulArgumentParser(['renew', '--force-interactive'], + {}) + arg_parser.add( + None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") + + with self.assertRaises(errors.Error): + arg_parser.parse_args() + + def test_parse_args_non_interactive_and_force_interactive(self): + arg_parser = HelpfulArgumentParser(['--force-interactive', + '--non-interactive'], {}) + arg_parser.add( + None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") + arg_parser.add( + None, "--non-interactive", dest="noninteractive_mode", + action="store_true" + ) + + with self.assertRaises(errors.Error): + arg_parser.parse_args() + + def test_parse_args_subset_names_wildcard_domain(self): + arg_parser = HelpfulArgumentParser(['--domain', + '*.example.com,potato.example.com', + '--allow-subset-of-names'], {}) + # The following arguments are added because they have to be defined + # in order for arg_parser to run completely. They are not used for the + # test. + arg_parser.add( + None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") + arg_parser.add( + None, "--non-interactive", dest="noninteractive_mode", + action="store_true") + arg_parser.add( + None, "--staging" + ) + arg_parser.add(None, "--dry-run") + arg_parser.add(None, "--csr") + arg_parser.add(None, "--must-staple") + arg_parser.add(None, "--validate-hooks") + + arg_parser.add(None, "-d", "--domain", dest="domains", + metavar="DOMAIN", action=_DomainsAction) + arg_parser.add(None, "--allow-subset-of-names") + # with self.assertRaises(errors.Error): + # arg_parser.parse_args() + + def test_parse_args_hosts_and_auto_hosts(self): + arg_parser = HelpfulArgumentParser(['--hsts', '--auto-hsts'], {}) + + arg_parser.add( + None, "--hsts", action="store_true", dest="hsts") + arg_parser.add( + None, "--auto-hsts", action="store_true", dest="auto_hsts") + # The following arguments are added because they have to be defined + # in order for arg_parser to run completely. They are not used for the + # test. + arg_parser.add( + None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") + arg_parser.add( + None, "--non-interactive", dest="noninteractive_mode", + action="store_true") + arg_parser.add(None, "--staging") + arg_parser.add(None, "--dry-run") + arg_parser.add(None, "--csr") + arg_parser.add(None, "--must-staple") + arg_parser.add(None, "--validate-hooks") + arg_parser.add(None, "--allow-subset-of-names") + with self.assertRaises(errors.Error): + arg_parser.parse_args() + + +if __name__ == '__main__': + unittest.main() # pragma: no cover