certbot/tests/compatibility/configurators/common.py

152 lines
5.5 KiB
Python
Raw Normal View History

2015-07-16 02:00:18 -04:00
"""Provides a common base for configurator proxies"""
2015-07-14 21:04:43 -04:00
import logging
import os
2015-07-21 21:14:57 -04:00
import shutil
2015-07-16 02:00:18 -04:00
import tempfile
2015-07-16 19:57:47 -04:00
import threading
2015-07-14 21:04:43 -04:00
import docker
from tests.compatibility import errors
from tests.compatibility import util
logger = logging.getLogger(__name__)
2015-07-16 02:00:18 -04:00
class Proxy(object):
2015-07-14 21:04:43 -04:00
# 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"""
2015-07-16 02:00:18 -04:00
if Proxy._NOT_ADDED_ARGS:
group = parser.add_argument_group("docker")
2015-07-14 21:04:43 -04:00
group.add_argument(
2015-07-16 02:00:18 -04:00
"--docker-url", default="unix://var/run/docker.sock",
help="URL of the docker server")
2015-07-14 21:04:43 -04:00
group.add_argument(
2015-07-16 02:00:18 -04:00
"--no-remove", action="store_true",
help="do not delete container on program exit")
Proxy._NOT_ADDED_ARGS = False
2015-07-14 21:04:43 -04:00
def __init__(self, args):
"""Initializes the plugin with the given command line args"""
2015-07-21 21:14:57 -04:00
self._temp_dir = tempfile.mkdtemp()
self.le_config = util.create_le_config(self._temp_dir)
config_dir = util.extract_configs(args.configs, self._temp_dir)
self._configs = [
os.path.join(config_dir, config)
for config in os.listdir(config_dir)]
2015-07-14 21:04:43 -04:00
self.args = args
self._docker_client = docker.Client(
2015-07-16 02:00:18 -04:00
base_url=self.args.docker_url, version="auto")
2015-07-14 21:04:43 -04:00
self.http_port, self.https_port = util.get_two_free_ports()
2015-07-16 19:57:47 -04:00
self._container_id = self._log_thread = None
2015-07-14 21:04:43 -04:00
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)
2015-07-16 19:57:47 -04:00
self._log_thread.join()
2015-07-14 21:04:43 -04:00
if not self.args.no_remove:
self._docker_client.remove_container(self._container_id)
2015-07-17 19:21:17 -04:00
def load_config(self):
2015-07-14 21:04:43 -04:00
"""Returns the next config directory to be tested"""
2015-07-21 21:14:57 -04:00
return self._configs.pop()
2015-07-14 21:04:43 -04:00
2015-07-21 21:14:57 -04:00
def start_docker(self, image_name, command):
2015-07-14 21:04:43 -04:00
"""Creates and runs a Docker container with the specified image"""
2015-07-21 21:14:57 -04:00
logger.info("Pulling Docker image. This may take a minute.")
2015-07-14 21:04:43 -04:00
for line in self._docker_client.pull(image_name, stream=True):
logger.debug(line)
host_config = docker.utils.create_host_config(
binds={
2015-07-21 21:14:57 -04:00
self._temp_dir : {"bind" : self._temp_dir, "mode" : "rw"}},
2015-07-14 21:04:43 -04:00
port_bindings={
2015-07-16 02:00:18 -04:00
80 : ("127.0.0.1", self.http_port),
443 : ("127.0.0.1", self.https_port)},)
2015-07-14 21:04:43 -04:00
container = self._docker_client.create_container(
2015-07-21 21:14:57 -04:00
image_name, command, ports=[80, 443], volumes=self._temp_dir,
2015-07-14 21:04:43 -04:00
host_config=host_config)
2015-07-16 02:00:18 -04:00
if container["Warnings"]:
logger.warning(container["Warnings"])
self._container_id = container["Id"]
2015-07-14 21:04:43 -04:00
self._docker_client.start(self._container_id)
2015-07-16 19:57:47 -04:00
self._log_thread = threading.Thread(target=self._start_log_thread)
self._log_thread.start()
2015-07-14 21:04:43 -04:00
def _start_log_thread(self):
2015-07-16 02:00:18 -04:00
client = docker.Client(base_url=self.args.docker_url, version="auto")
2015-07-14 21:04:43 -04:00
for line in client.logs(self._container_id, stream=True):
2015-07-17 19:21:17 -04:00
logger.debug(line.rstrip())
2015-07-16 02:00:18 -04:00
def check_call_in_docker(
self, command, *args, **kwargs): # pylint: disable=unused-argument
"""Simulates a call to check_call but executes the command in the
running docker image
"""
if self.popen_in_docker(command).returncode:
raise errors.Error(
"{0} exited with a nonzero value".format(command))
def popen_in_docker(
self, command, *args, **kwargs): # pylint: disable=unused-argument
"""Simulates a call to Popen but executes the command in the
running docker image
"""
class SimplePopen(object):
# pylint: disable=too-few-public-methods
"""Simplified Popen object"""
def __init__(self, returncode, output):
self.returncode = returncode
self._stdout = output
self._stderr = output
def communicate(self):
"""Returns stdout and stderr"""
return self._stdout, self._stderr
if isinstance(command, list):
command = " ".join(command)
2015-07-16 19:57:47 -04:00
returncode, output = self.execute_in_docker(command)
2015-07-16 02:00:18 -04:00
return SimplePopen(returncode, output)
2015-07-16 19:57:47 -04:00
def execute_in_docker(self, command):
2015-07-16 02:00:18 -04:00
"""Executes command inside the running docker image"""
2015-07-21 21:14:57 -04:00
logger.debug("Executing '%s'", command)
2015-07-16 02:00:18 -04:00
exec_id = self._docker_client.exec_create(self._container_id, command)
output = self._docker_client.exec_start(exec_id)
returncode = self._docker_client.exec_inspect(exec_id)["ExitCode"]
return returncode, output
2015-07-21 21:14:57 -04:00
def copy_certs_and_keys(self, cert_path, key_path, chain_path=None):
"""Copies certs and keys into the temporary directory"""
cert_and_key_dir = os.path.join(self._temp_dir, "certs_and_keys")
os.mkdir(cert_and_key_dir)
cert = os.path.join(cert_and_key_dir, "cert")
shutil.copy(cert_path, cert)
key = os.path.join(cert_and_key_dir, "key")
shutil.copy(key_path, key)
if chain_path:
chain = os.path.join(cert_and_key_dir, "chain")
shutil.copy(chain_path, chain)
else:
chain = None
return cert, key, chain