Source code for nuka.hosts.docker_host

# -*- coding: utf-8 -*-
from functools import partial
import subprocess
import asyncio
import sys

import docker as docker_py

from nuka.task import get_task_from_stack
from nuka.hosts.base import BaseHost
from nuka.hosts.base import HostGroup
from nuka.task import wait_for_boot
import nuka

try:
    DockerClient = docker_py.Client
except AttributeError:
    DockerClient = docker_py.APIClient


[docs]class DockerContainer(BaseHost): """By default the image will be ``bearstech/nukai:latest`` which is the latest debian with python3 installed but you can use whatever you want. We also provide a bunch of `nukai(mages) <https://hub.docker.com/r/bearstech/nukai/tags/>`_ .. code-block:: py >>> host = DockerContainer( ... hostname='myhost', ... image='bearstech/nukai:debian-jessie-python3') """ provider = 'docker' def __init__(self, hostname=None, image='bearstech/nukai:latest', **kwargs): kwargs.update(hostname=hostname, image=image) super().__init__(**kwargs) self.cli = DockerClient() @property def bootstrap_command(self): if 'bootstrap_command' in self.vars: return self.vars['bootstrap_command'] elif self.vars['image'].startswith('debian'): return ( 'which python || (apt-get update -qq > /dev/null; ' ' apt-get install -qqy ' 'python-virtualenv wget perl-modules adduser)' ) elif self.vars['image'].startswith('centos'): return ( 'ls /usr/bin/wget 2>&1 > /dev/null || ' 'yum install -y -q -q wget 2>&1 > /dev/null;' 'ls /usr/bin/virtualenv 2>&1 > /dev/null || ' 'yum install -y -q -q python-virtualenv 2>&1 > /dev/null' ) async def acquire_session_slot(self): return async def acquire_connection_slot(self): return def free_session_slot(self): return def wraps_command_line(self, cmd, **kwargs): # there is no ssh in docker so we use the regular switch_user ssh_user = kwargs.get('switch_ssh_user') if ssh_user is not None: kwargs['switch_user'] = ssh_user switch_user = kwargs.get('switch_user') or 'root' if switch_user != 'root': if switch_user != self.vars['user']: # we have to use sudo args = (switch_user, cmd) if self.use_sudo: cmd = '{sudo} -u {0} {1}'.format(*args, **nuka.config) else: cmd = '{su} -c "{1}" {0}'.format(*args, **nuka.config) elif self.use_sudo: cmd = '{sudo} {0}'.format(cmd, **nuka.config) cmd = ['docker', 'exec', '-i', str(self), 'bash', '-c', cmd] return cmd
[docs] async def boot(self): # we need to get the task from here. # we cant retrieve it while in an executor task = get_task_from_stack() boot = partial(self._boot_api, task=task) return await self.loop.run_in_executor(None, boot)
def _boot_api(self, task=None): with self.timeit(type='api_call', task=task, name='start()'): try: self.cli.start(self.name) except docker_py.errors.APIError: pass with self.timeit(type='api_call', task=task, name='containers()'): containers = self.cli.containers(filters=dict(name=self.name)) if containers: container = containers[0] else: with self.timeit(type='api_call', task=task, name='images()'): found = False for image in self.cli.images(): if image['RepoTags'] == [self.image]: found = True if not found: with self.timeit(type='api_call', task=task, name='pull()'): self.log.warning('Pulling image {0}...'.format(self.image)) subprocess.call(['docker', 'pull', self.image]) self.log.debug('Create container...') with self.timeit(type='api_call', task=task, name='create()'): container = self.cli.create_container( image=self.image, name=self.name, hostname=self.name, command=self.vars.get('command', None)) self.vars['container'] = container self.vars['container_id'] = container['Id'] try: self.cli.start(self.name) except docker_py.errors.APIError: pass else: self.log.debug('Container started'.format(self)) return container @property def private_ip(self): if 'private_ip' not in self.vars: container = self.vars['container'] net = list(container['NetworkSettings']['Networks'].values())[0] ip = net['IPAddress'] # containers has no public ip self.vars['public_ip'] = self.vars['private_ip'] = ip return self.vars['private_ip'] public_ip = private_ip
[docs] async def destroy(self): remove_container = partial(self.cli.remove_container, self.hostname, force=True) await self.loop.run_in_executor(None, remove_container)
[docs]class DockerCompose(HostGroup): """A HostGroup that use the docker-compose.yml to provide some hosts: .. code-block:: py >>> hosts = DockerCompose(project_name='myproject') """ def __init__(self, project_name=None, compose_file=None): self.project_name = project_name self.compose_file = compose_file self.args = [] if self.project_name: self.args.extend(['--project-name', self.project_name]) if self.compose_file: self.args.extend(['--file', self.compose_file]) super().__init__()
[docs] async def boot(self): """launch ``docker-compose up`` and setup all containers""" cmd = [sys.executable, '-m', 'compose'] + self.args + ['up', '-d'] subprocess.check_call(cmd) cmd = [sys.executable, '-m', 'compose'] + self.args + ['ps'] stdout = subprocess.check_output( cmd, stderr=subprocess.STDOUT) stdout = stdout.decode('utf8') for line in stdout.split('\n')[2:]: if line.strip(): name = line.split()[0] self[name] = DockerContainer(hostname=name) if self: await asyncio.wait([wait_for_boot(h) for h in self.values()])
[docs] async def destroy(self): """launch ``docker-compose down``""" cmd = [sys.executable, '-m', 'compose'] + self.args + ['down'] p = subprocess.Popen(cmd) p.wait() for host in self.values(): host.vars['destroyed'] = True return True
def update_image(base_image): container = DockerContainer( hostname='nuka', image=base_image, command=['bash', '-c', 'while true; do sleep 1000000000000; done'], ) nuka.run(wait_for_boot(host=container)) image, tag = base_image.split(':', 1) container.cli.commit( container.container_id, repository='nuka_' + image, tag=tag) return container def clean_images(): cli = docker_py.Client() for image in cli.images(): if image['RepoTags'] == ['<none>:<none>']: print('Removing {Id}'.format(**image)) cli.remove_image(image['Id'])