Source code for ebonite.ext.docker.runner

import sys
import time
from typing import Dict, Generator, Type

import docker.errors

from ebonite.build.runner.base import RunnerBase
from ebonite.utils.log import logger

from .base import DockerContainer, DockerEnv, DockerImage


[docs]class DockerRunnerException(Exception): pass
[docs]class DockerRunner(RunnerBase): """RunnerBase implementation for docker containers"""
[docs] def instance_exists(self, instance: DockerContainer, env: DockerEnv, **kwargs) -> bool: with env.daemon.client() as client: try: client.containers.get(instance.name) return True except docker.errors.NotFound: return False
[docs] def remove_instance(self, instance: DockerContainer, env: DockerEnv, **kwargs): with env.daemon.client() as client: try: c = client.containers.get(instance.name) c.remove(**kwargs) except docker.errors.NotFound: pass
[docs] def instance_type(self) -> Type[DockerContainer]: return DockerContainer
[docs] def create_instance(self, name: str, port_mapping: Dict[int, int] = None, **kwargs) -> DockerContainer: return DockerContainer(name, port_mapping, kwargs)
[docs] def run(self, instance: DockerContainer, image: DockerImage, env: DockerEnv, rm=True, detach=True, **kwargs): if not (isinstance(instance, DockerContainer) and isinstance(image, DockerImage) and isinstance(env, DockerEnv)): raise TypeError('DockerRunner works with DockerContainer, DockerImage and DockerHost only') with env.daemon.client() as client: image.registry.login(client) try: # always detach from container and just stream logs if detach=False container = client.containers.run(image.uri, name=instance.name, auto_remove=rm, ports=instance.port_mapping, detach=True, **instance.params, **kwargs) instance.container_id = container.id if not detach: try: # infinite loop of logs while container running or if everything ok for log in self._logs(container, stream=True): logger.debug(log) self._sleep() if not self._is_service_running(client, instance.name): raise DockerRunnerException("The container died unexpectedly.") except KeyboardInterrupt: logger.info('Interrupted. Stopping the container') container.stop() else: self._sleep(.5) if not self._is_service_running(client, instance.name): if not rm: for log in self._logs(container, stdout=False, stderr=True): raise DockerRunnerException("The container died unexpectedly.", log) else: # Can't get logs from removed container raise DockerRunnerException("The container died unexpectedly. Try to run the container " "with detach=False or rm=False args to get more info.") except docker.errors.ContainerError as e: if e.stderr: print(e.stderr.decode(), file=sys.stderr) raise
[docs] def logs(self, instance: DockerContainer, env: DockerEnv, **kwargs) -> Generator[str, None, None]: self._validate(instance, env) with env.daemon.client() as client: container = client.containers.get(instance.name) yield from self._logs(container, **kwargs)
def _logs(self, container, stdout=True, stderr=True, stream=False, tail='all', since=None, follow=None, until=None, **kwargs) -> Generator[str, None, None]: log = container.logs(stdout=stdout, stderr=stderr, stream=stream, tail=tail, since=since, follow=follow, until=until) if stream: for line in log: yield line.decode("utf-8") else: yield log.decode("utf-8") def _sleep(self, timeout: float = 10): time.sleep(timeout) def _is_service_running(self, client, name): from docker.errors import NotFound try: container = client.containers.get(name) return container.status == 'running' except NotFound: return False
[docs] def is_running(self, instance: DockerContainer, env: DockerEnv, **kwargs) -> bool: self._validate(instance, env) with env.daemon.client() as client: return self._is_service_running(client, instance.name)
[docs] def stop(self, instance: DockerContainer, env: DockerEnv, **kwargs): self._validate(instance, env) with env.daemon.client() as client: try: container = client.containers.get(instance.name) container.stop() except docker.errors.NotFound: pass
@classmethod def _validate(cls, instance: DockerContainer, env: DockerEnv): if not (isinstance(instance, DockerContainer) and isinstance(env, DockerEnv)): raise TypeError('DockerRunner works with DockerContainer and DockerHost only')