mirror of
https://github.com/certbot/certbot.git
synced 2026-01-23 07:20:55 +03:00
Started implementation of Apache base
This commit is contained in:
2
setup.py
2
setup.py
@@ -60,7 +60,6 @@ docs_extras = [
|
||||
|
||||
testing_extras = [
|
||||
'coverage',
|
||||
'docker-py',
|
||||
'nose',
|
||||
'nosexcover',
|
||||
'tox',
|
||||
@@ -107,7 +106,6 @@ setup(
|
||||
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'compatibility = tests.compatibility.plugin_test:main [testing]',
|
||||
'letsencrypt = letsencrypt.cli:main',
|
||||
'letsencrypt-renewer = letsencrypt.renewer:main',
|
||||
],
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Let's Encrypt Tests"""
|
||||
"""Let's Encrypt tests"""
|
||||
|
||||
@@ -15,6 +15,6 @@ ENV APACHE_CONFDIR=/tmp/apache2 \
|
||||
APACHE_LOCK_DIR=/var/lock \
|
||||
APACHE_LOG_DIR=/usr/local/apache2/logs
|
||||
|
||||
COPY a2enmod.sh /usr/local/bin/
|
||||
COPY tests/compatibility/a2enmod.sh /usr/local/bin/
|
||||
|
||||
CMD [ "httpd-foreground" ]
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Let's Encrypt Compatibility Test"""
|
||||
"""Let's Encrypt compatibility test"""
|
||||
|
||||
1
tests/compatibility/configurators/__init__.py
Normal file
1
tests/compatibility/configurators/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Let's Encrypt compatibility test configurators"""
|
||||
1
tests/compatibility/configurators/apache/__init__.py
Normal file
1
tests/compatibility/configurators/apache/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Let's Encrypt compatibility test Apache configurators"""
|
||||
34
tests/compatibility/configurators/apache/common.py
Normal file
34
tests/compatibility/configurators/apache/common.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Provides a common base for Apache tests"""
|
||||
import mock
|
||||
|
||||
from tests.compatibilty import configurators
|
||||
|
||||
class ApacheConfiguratorCommonTester(configurators.common.ConfiguratorTester):
|
||||
"""A common base for Apache test configurators"""
|
||||
|
||||
def __init__(self, args):
|
||||
"""Initializes the plugin with the given command line args"""
|
||||
super(ApacheConfiguratorCommonTester, self).__init__(args)
|
||||
self._patch = mock.patch('letsencrypt_apache.configurator.subprocess')
|
||||
self._mock = self._patch.start()
|
||||
self._mock.check_call = self._check_call
|
||||
self._apache_configurator = None
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Wraps the Apache Configurator methods"""
|
||||
method = getattr(self._apache_configurator, name, None)
|
||||
if callable(method):
|
||||
return method
|
||||
else:
|
||||
raise AttributeError()
|
||||
|
||||
def _check_call(self, command, *args, **kwargs):
|
||||
"""A function to mock the call to subprocess.check_call"""
|
||||
|
||||
def load_config(self):
|
||||
"""Loads the next configuration for the plugin to test"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_test_domain_names(self):
|
||||
"""Returns a list of domain names to test against the plugin"""
|
||||
raise NotImplementedError()
|
||||
95
tests/compatibility/configurators/common.py
Normal file
95
tests/compatibility/configurators/common.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Provides a common base for compatibility test configurators"""
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
import docker
|
||||
|
||||
from tests.compatibility import errors
|
||||
from tests.compatibility import util
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfiguratorTester(object):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
"""A common base for compatibility test configurators"""
|
||||
|
||||
_NOT_ADDED_ARGS = True
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, parser):
|
||||
"""Adds command line arguments needed by the plugin"""
|
||||
if ConfiguratorTester._NOT_ADDED_ARGS:
|
||||
group = parser.add_argument_group('docker')
|
||||
group.add_argument(
|
||||
'--docker-url', default='unix://var/run/docker.sock',
|
||||
help='URL of the docker server')
|
||||
group.add_argument(
|
||||
'--no-remove', action='store_true',
|
||||
help='do not delete container on program exit')
|
||||
ConfiguratorTester._NOT_ADDED_ARGS = False
|
||||
|
||||
def __init__(self, args):
|
||||
"""Initializes the plugin with the given command line args"""
|
||||
self.temp_dir = util.setup_temp_dir(args.configs)
|
||||
self.config_dir = os.path.join(self.temp_dir, util.CONFIG_DIR)
|
||||
self._configs = os.listdir(self.config_dir)
|
||||
|
||||
self.args = args
|
||||
self._docker_client = docker.Client(
|
||||
base_url=self.args.docker_url, version='auto')
|
||||
self.http_port, self.https_port = util.get_two_free_ports()
|
||||
self._container_id = self._log_process = None
|
||||
|
||||
def has_more_configs(self):
|
||||
"""Returns true if there are more configs to test"""
|
||||
return bool(self._configs)
|
||||
|
||||
def cleanup_from_tests(self):
|
||||
"""Performs any necessary cleanup from running plugin tests"""
|
||||
self._docker_client.stop(self._container_id)
|
||||
self._log_process.join()
|
||||
if not self.args.no_remove:
|
||||
self._docker_client.remove_container(self._container_id)
|
||||
|
||||
def get_next_config(self):
|
||||
"""Returns the next config directory to be tested"""
|
||||
return self._configs.pop()
|
||||
|
||||
def start_docker(self, image_name):
|
||||
"""Creates and runs a Docker container with the specified image"""
|
||||
for line in self._docker_client.pull(image_name, stream=True):
|
||||
logger.debug(line)
|
||||
|
||||
host_config = docker.utils.create_host_config(
|
||||
binds={
|
||||
self.config_dir : {'bind' : self.config_dir, 'mode' : 'rw'}},
|
||||
port_bindings={
|
||||
80 : ('127.0.0.1', self.http_port),
|
||||
443 : ('127.0.0.1', self.https_port)},)
|
||||
container = self._docker_client.create_container(
|
||||
image_name, ports=[80, 443], volumes=self.config_dir,
|
||||
host_config=host_config)
|
||||
if container['Warnings']:
|
||||
logger.warning(container['Warnings'])
|
||||
self._container_id = container['Id']
|
||||
self._docker_client.start(self._container_id)
|
||||
|
||||
self._log_process = multiprocessing.Process(
|
||||
target=self._start_log_thread)
|
||||
self._log_process.start()
|
||||
|
||||
def execute_in_docker(self, command):
|
||||
"""Executes command inside the running docker image"""
|
||||
exec_id = self._docker_client.exec_create(self._container_id, command)
|
||||
output = self._docker_client.exec_start(exec_id)
|
||||
if self._docker_client.exec_inspect(exec_id)['ExitCode']:
|
||||
raise errors.Error('Docker command \'{0}\' failed'.format(command))
|
||||
return output
|
||||
|
||||
def _start_log_thread(self):
|
||||
client = docker.Client(base_url=self.args.docker_url, version='auto')
|
||||
for line in client.logs(self._container_id, stream=True):
|
||||
logger.debug(line)
|
||||
5
tests/compatibility/errors.py
Normal file
5
tests/compatibility/errors.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Let's Encrypt compatibility test errors"""
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Generic Let's Encrypt compatibility test error"""
|
||||
53
tests/compatibility/interfaces.py
Normal file
53
tests/compatibility/interfaces.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Let's Encrypt compatibility test interfaces"""
|
||||
import zope.interface
|
||||
|
||||
import letsencrypt.interfaces
|
||||
|
||||
|
||||
class IPluginTester(zope.interface.Interface):
|
||||
"""Wraps a Let's Encrypt plugin"""
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, parser):
|
||||
"""Adds command line arguments needed by the parser"""
|
||||
|
||||
def __init__(self, args):
|
||||
"""Initializes the plugin with the given command line args"""
|
||||
|
||||
def cleanup_from_tests(self):
|
||||
"""Performs any necessary cleanup from running plugin tests.
|
||||
|
||||
This is guarenteed to be called before the program exits.
|
||||
|
||||
"""
|
||||
|
||||
def has_more_configs(self):
|
||||
"""Returns True if there are more configs to test"""
|
||||
|
||||
def load_config(self):
|
||||
"""Loads the next configuration for the plugin to test"""
|
||||
|
||||
|
||||
class IConfiguratorBaseTester(IPluginTester):
|
||||
"""Common functionality for authenticator/installer tests"""
|
||||
http_port = zope.interface.Attribute(
|
||||
'The port to connect to on localhost for HTTP traffic')
|
||||
|
||||
https_port = zope.interface.Attribute(
|
||||
'The port to connect to on localhost for HTTPS traffic')
|
||||
|
||||
def get_test_domain_names(self):
|
||||
"""Returns a list of domain names to test against the plugin"""
|
||||
|
||||
|
||||
class IAuthenticatorTester(
|
||||
IConfiguratorBaseTester, letsencrypt.interfaces.IAuthenticator):
|
||||
"""Wraps a Let's Encrypt authenticator"""
|
||||
|
||||
|
||||
class IInstallerTester(
|
||||
IConfiguratorBaseTester, letsencrypt.interfaces.IInstaller):
|
||||
"""Wraps a Let's Encrypt installer"""
|
||||
|
||||
|
||||
class IConfiguratorTester(IAuthenticatorTester, IInstallerTester):
|
||||
"""Wraps a Let's Encrypt configurator"""
|
||||
@@ -1,49 +0,0 @@
|
||||
"""Module for parsing command line arguments and config files."""
|
||||
import argparse
|
||||
|
||||
|
||||
DESCRIPTION = """
|
||||
Tests Let's Encrypt plugins against different web servers and configurations
|
||||
using Docker images. It is assumed that Docker is already installed.
|
||||
|
||||
"""
|
||||
|
||||
def parse_args():
|
||||
"""Returns parsed command line arguments."""
|
||||
parser = argparse.ArgumentParser(description=DESCRIPTION)
|
||||
|
||||
add_args(parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.redirect:
|
||||
args.names = True
|
||||
args.install = True
|
||||
elif args.install:
|
||||
args.names = True
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def add_args(parser):
|
||||
"""Adds general/program wide arguments to the group."""
|
||||
group = parser.add_argument_group("general")
|
||||
group.add_argument(
|
||||
"-t", "--tar", default="configs.tar.gz",
|
||||
help="a gzipped tarball containing server configurations")
|
||||
group.add_argument(
|
||||
"-p", "--plugin", default="apache",
|
||||
help="the plugin to be tested")
|
||||
group.add_argument(
|
||||
"-n", "--names", action="store_true", help="tests installer's domain "
|
||||
"name identification")
|
||||
group.add_argument(
|
||||
"-a", "--auth", action="store_true", help="tests authenticators")
|
||||
group.add_argument(
|
||||
"-i", "--install", action="store_true", help="tests installer's "
|
||||
"certificate installation (implicitly includes -d)")
|
||||
group.add_argument(
|
||||
"-r", "--redirect", action="store_true", help="tests installer's "
|
||||
"redirecting HTTP to HTTPS (implicitly includes -di)")
|
||||
group.add_argument(
|
||||
"--no-simple-http-tls", action="store_true", help="do not use TLS "
|
||||
"when solving SimpleHTTP challenges")
|
||||
@@ -1,13 +1,78 @@
|
||||
"""Tests Let's Encrypt plugins against different server configurations."""
|
||||
import parser
|
||||
import util
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
|
||||
from tests.compatibility.configurators import common
|
||||
|
||||
DESCRIPTION = """
|
||||
Tests Let's Encrypt plugins against different server configuratons. It is
|
||||
assumed that Docker is already installed.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
PLUGINS = {'common' : common.ConfiguratorTester}
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_args():
|
||||
"""Returns parsed command line arguments."""
|
||||
parser = argparse.ArgumentParser(description=DESCRIPTION)
|
||||
|
||||
group = parser.add_argument_group('general')
|
||||
group.add_argument(
|
||||
'-c', '--configs', default='configs.tar.gz',
|
||||
help='a directory or tarball containing server configurations')
|
||||
group.add_argument(
|
||||
'-p', '--plugin', default='apache', help='the plugin to be tested')
|
||||
group.add_argument(
|
||||
'-a', '--auth', action='store_true',
|
||||
help='tests the plugin as an authenticator')
|
||||
group.add_argument(
|
||||
'-i', '--install', action='store_true',
|
||||
help='tests the plugin as an installer')
|
||||
group.add_argument(
|
||||
'-r', '--redirect', action='store_true', help='tests the plugin\'s '
|
||||
'ability to redirect HTTP to HTTPS (implicitly includes installer '
|
||||
'tests)')
|
||||
|
||||
for plugin in PLUGINS.itervalues():
|
||||
plugin.add_parser_arguments(parser)
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.redirect:
|
||||
args.install = True
|
||||
elif not (args.auth or args.install):
|
||||
args.auth = args.install = args.redirect = True
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Prepares logging for the program"""
|
||||
fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s"
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(logging.Formatter(fmt))
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
root_logger.addHandler(handler)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main test script execution."""
|
||||
args = parser.parse_args()
|
||||
setup_logging()
|
||||
args = get_args()
|
||||
plugin = PLUGINS[args.plugin](args)
|
||||
plugin.start_docker('bradmw/apache2.4')
|
||||
config = os.path.join(plugin.config_dir, 'apache2')
|
||||
config_file = os.path.join(config, 'apache2.conf')
|
||||
plugin.execute_in_docker('apachectl -d {0} -f {1} -k restart'.format(config, config_file))
|
||||
#plugin.cleanup_from_tests()
|
||||
|
||||
print util.setup_tmp_dir(args.tar)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,21 +1,41 @@
|
||||
"""Utility functions for Let's Encrypt plugin tests."""
|
||||
import contextlib
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import tarfile
|
||||
import tempfile
|
||||
|
||||
|
||||
TEMP_DIRECTORY = tempfile.mkdtemp()
|
||||
# Location of decompressed server root configurations
|
||||
CONFIGS = os.path.join(TEMP_DIRECTORY, "configs")
|
||||
SCRIPTS = os.path.join(TEMP_DIRECTORY, "scripts")
|
||||
from tests.compatibility import errors
|
||||
|
||||
|
||||
def setup_tmp_dir(tar_path):
|
||||
"""Sets up a temporary directory for this run and returns its path."""
|
||||
tar = tarfile.open(tar_path, "r:gz")
|
||||
tar.extractall(os.path.join(tmp_dir, SERVER_ROOTS))
|
||||
# Paths used in the program relative to the temp directory
|
||||
CONFIG_DIR = "configs"
|
||||
LE_CONFIG = os.path.join("letsencrypt", "config")
|
||||
LE_LOGS = os.path.join("letsencrypt", "logs")
|
||||
|
||||
os.makedirs(os.path.join(tmp_dir, "mnt"))
|
||||
os.makedirs(os.path.join(tmp_dir, "scripts"))
|
||||
|
||||
return tmp_dir
|
||||
def setup_temp_dir(configs):
|
||||
"""Sets up a temporary directory and extracts server configs"""
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
config_dir = os.path.join(temp_dir, CONFIG_DIR)
|
||||
|
||||
if os.path.isdir(configs):
|
||||
shutil.copytree(configs, config_dir, symlinks=True)
|
||||
elif tarfile.is_tarfile(configs):
|
||||
with tarfile.open(configs, 'r') as tar:
|
||||
tar.extractall(config_dir)
|
||||
else:
|
||||
raise errors.Error('Unknown configurations file type')
|
||||
|
||||
return temp_dir
|
||||
|
||||
|
||||
def get_two_free_ports():
|
||||
"""Returns two free ports to use for the tests"""
|
||||
with contextlib.closing(socket.socket()) as sock1:
|
||||
with contextlib.closing(socket.socket()) as sock2:
|
||||
sock1.bind(('', 0))
|
||||
sock2.bind(('', 0))
|
||||
|
||||
return sock1.getsockname()[1], sock2.getsockname()[1]
|
||||
|
||||
23
tests/setup.py
Normal file
23
tests/setup.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
install_requires = [
|
||||
'letsencrypt',
|
||||
'letsencrypt-apache',
|
||||
'letsencrypt-nginx',
|
||||
'docker-py',
|
||||
'mock<1.1.0', # py26
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setup(
|
||||
name='compatibility-test',
|
||||
packages=find_packages(),
|
||||
install_requires=install_requires,
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'compatibility-test = compatibility.plugin_test:main',
|
||||
],
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user