mirror of
https://github.com/certbot/certbot.git
synced 2026-06-08 08:12:15 -04:00
Merge branch 'master' into windows-auto-update
This commit is contained in:
commit
067438f3c2
116 changed files with 2131 additions and 3544 deletions
|
|
@ -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)
|
||||
|
|
|
|||
19
.travis.yml
19
.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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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 + '.')
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
||||
|
|
|
|||
|
|
@ -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'.
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
526
certbot/certbot/_internal/cli/__init__.py
Normal file
526
certbot/certbot/_internal/cli/__init__.py
Normal file
|
|
@ -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
|
||||
107
certbot/certbot/_internal/cli/cli_constants.py
Normal file
107
certbot/certbot/_internal/cli/cli_constants.py
Normal file
|
|
@ -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",))}
|
||||
239
certbot/certbot/_internal/cli/cli_utils.py
Normal file
239
certbot/certbot/_internal/cli/cli_utils.py
Normal file
|
|
@ -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
|
||||
19
certbot/certbot/_internal/cli/group_adder.py
Normal file
19
certbot/certbot/_internal/cli/group_adder.py
Normal file
|
|
@ -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"])
|
||||
468
certbot/certbot/_internal/cli/helpful.py
Normal file
468
certbot/certbot/_internal/cli/helpful.py
Normal file
|
|
@ -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 <other> | Conflict error | Use <other> |
|
||||
|
||||
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}
|
||||
50
certbot/certbot/_internal/cli/paths_parser.py
Normal file
50
certbot/certbot/_internal/cli/paths_parser.py
Normal file
|
|
@ -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"))
|
||||
97
certbot/certbot/_internal/cli/plugins_parsing.py
Normal file
97
certbot/certbot/_internal/cli/plugins_parsing.py
Normal file
|
|
@ -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 <plugin_name> 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)
|
||||
27
certbot/certbot/_internal/cli/report_config_interaction.py
Normal file
27
certbot/certbot/_internal/cli/report_config_interaction.py
Normal file
|
|
@ -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)
|
||||
72
certbot/certbot/_internal/cli/subparsers.py
Normal file
72
certbot/certbot/_internal/cli/subparsers.py
Normal file
|
|
@ -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.")
|
||||
106
certbot/certbot/_internal/cli/verb_help.py
Normal file
106
certbot/certbot/_internal/cli/verb_help.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -33,12 +33,16 @@ 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
|
||||
=============================
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
193
certbot/tests/helpful_test.py
Normal file
193
certbot/tests/helpful_test.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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} '
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
include LICENSE.txt
|
||||
include README.rst
|
||||
recursive-include docs *
|
||||
recursive-include letshelp_certbot/testdata *
|
||||
|
|
@ -1 +0,0 @@
|
|||
Let's help Certbot client
|
||||
1
letshelp-certbot/docs/.gitignore
vendored
1
letshelp-certbot/docs/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
/_build/
|
||||
|
|
@ -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 <target>' where <target> 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."
|
||||
0
letshelp-certbot/docs/_static/.gitignore
vendored
0
letshelp-certbot/docs/_static/.gitignore
vendored
0
letshelp-certbot/docs/_templates/.gitignore
vendored
0
letshelp-certbot/docs/_templates/.gitignore
vendored
|
|
@ -1,8 +0,0 @@
|
|||
=================
|
||||
API Documentation
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
api/**
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
:mod:`letshelp_certbot`
|
||||
---------------------------
|
||||
|
||||
.. automodule:: letshelp_certbot
|
||||
:members:
|
||||
|
||||
:mod:`letshelp_certbot.apache`
|
||||
==================================
|
||||
|
||||
.. automodule:: letshelp_certbot.apache
|
||||
:members:
|
||||
|
|
@ -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
|
||||
# "<project> v<release> 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 <link> 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),
|
||||
}
|
||||
|
|
@ -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`
|
||||
|
||||
|
|
@ -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 ^<target^>` where ^<target^> 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
|
||||
|
|
@ -1 +0,0 @@
|
|||
"""Tools for submitting server configurations"""
|
||||
|
|
@ -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 # pylint: disable=unused-import, no-name-in-module
|
||||
|
||||
_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
|
||||
|
|
@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue