mirror of
https://github.com/certbot/certbot.git
synced 2026-06-04 22:33:00 -04:00
This PR is the part 4 to implement #6541. It adds the integration tests for the nginx certbot plugin, and corresponds to the certbot-ci translation of certbot-nginx/tests/boulder-integration.sh that is executed for each PR. As with certbot core tests, tests are written in Python, and executed by pytest, against a dynamic Boulder/Pebble instance setup. Tests are parallelized, of course, and a specific IntegrationTestsContext class, extended the one from certbot core tests, is crafter for these specific tests: its main goal is to setup a specific nginx instance for the current test. On top of that, I use the test parametrization feature of Pytest, to drastically reduce the size of the actual code: indeed, the 6 tests from the original bash script share the same logic. So using a parametrization, one unique test is written, that is then executed 6 times against 6 different sets of parameters. Note that the module integration_tests.nginx_tests.nginx_config do the same, but in Python, than certbot-nginx/tests/boulder-integration.conf.sh. The latter will be removed in a future PR, with all other bash scripts. * Add nginx tests * Distribute the other_port * Load a pre-generated key/cert for nginx config * Correct preload, remove a test, simplify a variable * Integrate assertion directly in the test function * Check process is not terminated * Add spaces in the nginx config * Add comments * Use indirection * Allow external cert * Add coverage threshold for certbot-nginx
139 lines
5.8 KiB
Python
139 lines
5.8 KiB
Python
"""Module to handle the context of integration tests."""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from distutils.version import LooseVersion
|
|
|
|
from certbot_integration_tests.utils import misc
|
|
|
|
|
|
class IntegrationTestsContext(object):
|
|
"""General fixture describing a certbot integration tests context"""
|
|
def __init__(self, request):
|
|
self.request = request
|
|
|
|
if hasattr(request.config, 'slaveinput'): # Worker node
|
|
self.worker_id = request.config.slaveinput['slaveid']
|
|
acme_xdist = request.config.slaveinput['acme_xdist']
|
|
else: # Primary node
|
|
self.worker_id = 'primary'
|
|
acme_xdist = request.config.acme_xdist
|
|
|
|
self.acme_server =acme_xdist['acme_server']
|
|
self.directory_url = acme_xdist['directory_url']
|
|
self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id]
|
|
self.http_01_port = acme_xdist['http_port'][self.worker_id]
|
|
self.other_port = acme_xdist['other_port'][self.worker_id]
|
|
# Challtestsrv REST API, that exposes entrypoints to register new DNS entries,
|
|
# is listening on challtestsrv_port.
|
|
self.challtestsrv_port = acme_xdist['challtestsrv_port']
|
|
|
|
# Certbot version does not depend on the test context. But getting its value requires
|
|
# calling certbot from a subprocess. Since it will be called a lot of times through
|
|
# _common_test_no_force_renew, we cache its value as a member of the fixture context.
|
|
self.certbot_version = misc.get_certbot_version()
|
|
|
|
self.workspace = tempfile.mkdtemp()
|
|
self.config_dir = os.path.join(self.workspace, 'conf')
|
|
self.hook_probe = tempfile.mkstemp(dir=self.workspace)[1]
|
|
|
|
self.manual_dns_auth_hook = (
|
|
'{0} -c "import os; import requests; import json; '
|
|
"assert not os.environ.get('CERTBOT_DOMAIN').startswith('fail'); "
|
|
"data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN')),"
|
|
"'value':os.environ.get('CERTBOT_VALIDATION')}}; "
|
|
"request = requests.post('http://localhost:{1}/set-txt', data=json.dumps(data)); "
|
|
"request.raise_for_status(); "
|
|
'"'
|
|
).format(sys.executable, self.challtestsrv_port)
|
|
self.manual_dns_cleanup_hook = (
|
|
'{0} -c "import os; import requests; import json; '
|
|
"data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN'))}}; "
|
|
"request = requests.post('http://localhost:{1}/clear-txt', data=json.dumps(data)); "
|
|
"request.raise_for_status(); "
|
|
'"'
|
|
).format(sys.executable, self.challtestsrv_port)
|
|
|
|
def cleanup(self):
|
|
"""Cleanup the integration test context."""
|
|
shutil.rmtree(self.workspace)
|
|
|
|
def _common_test_no_force_renew(self, args):
|
|
"""
|
|
Base command to execute certbot in a distributed integration test context,
|
|
not renewing certificates by default.
|
|
"""
|
|
new_environ = os.environ.copy()
|
|
new_environ['TMPDIR'] = self.workspace
|
|
|
|
additional_args = []
|
|
if self.certbot_version >= LooseVersion('0.30.0'):
|
|
additional_args.append('--no-random-sleep-on-renew')
|
|
|
|
command = [
|
|
'certbot',
|
|
'--server', self.directory_url,
|
|
'--no-verify-ssl',
|
|
'--http-01-port', str(self.http_01_port),
|
|
'--https-port', str(self.tls_alpn_01_port),
|
|
'--manual-public-ip-logging-ok',
|
|
'--config-dir', self.config_dir,
|
|
'--work-dir', os.path.join(self.workspace, 'work'),
|
|
'--logs-dir', os.path.join(self.workspace, 'logs'),
|
|
'--non-interactive',
|
|
'--no-redirect',
|
|
'--agree-tos',
|
|
'--register-unsafely-without-email',
|
|
'--debug',
|
|
'-vv'
|
|
]
|
|
|
|
command.extend(args)
|
|
command.extend(additional_args)
|
|
|
|
print('Invoke command:\n{0}'.format(subprocess.list2cmdline(command)))
|
|
return subprocess.check_output(command, universal_newlines=True,
|
|
cwd=self.workspace, env=new_environ)
|
|
|
|
def _common_test(self, args):
|
|
"""
|
|
Base command to execute certbot in a distributed integration test context,
|
|
renewing certificates by default.
|
|
"""
|
|
command = ['--renew-by-default']
|
|
command.extend(args)
|
|
return self._common_test_no_force_renew(command)
|
|
|
|
def certbot_no_force_renew(self, args):
|
|
"""
|
|
Execute certbot with given args, not renewing certificates by default.
|
|
:param args: args to pass to certbot
|
|
:return: output of certbot execution
|
|
"""
|
|
command = ['--authenticator', 'standalone', '--installer', 'null']
|
|
command.extend(args)
|
|
return self._common_test_no_force_renew(command)
|
|
|
|
def certbot(self, args):
|
|
"""
|
|
Execute certbot with given args, renewing certificates by default.
|
|
:param args: args to pass to certbot
|
|
:return: output of certbot execution
|
|
"""
|
|
command = ['--renew-by-default']
|
|
command.extend(args)
|
|
return self.certbot_no_force_renew(command)
|
|
|
|
def get_domain(self, subdomain='le'):
|
|
"""
|
|
Generate a certificate domain name suitable for distributed certbot integration tests.
|
|
This is a requirement to let the distribution know how to redirect the challenge check
|
|
from the ACME server to the relevant pytest-xdist worker. This resolution is done by
|
|
appending the pytest worker id to the subdomain, using this pattern:
|
|
{subdomain}.{worker_id}.wtf
|
|
:param subdomain: the subdomain to use in the generated domain (default 'le')
|
|
:return: the well-formed domain suitable for redirection on
|
|
"""
|
|
return '{0}.{1}.wtf'.format(subdomain, self.worker_id)
|