Пример #1
0
class Runner:
    """
    This class is in charge of loading test suites and runs them on different environments

    """
    STOP_TIMEOUT = 3

    def __init__(self, cfg):
        from docker.client import Client
        from docker.utils import kwargs_from_env

        self.config = cfg
        docker_kwargs = kwargs_from_env()
        docker_kwargs['tls'].assert_hostname = False
        self.docker = Client(**docker_kwargs)


    def run(self, build, *tests):
        """
        Run all the test suites passed in as parameters on the given build
        This method will start a container of the build, run the tests and stop it

        """
        from docker.utils import create_host_config

        print("Running tests on {}".format(build.name))
        ports = self.config['environment']['ports']
        host = self.config['global'].get('docker_host', os.getenv('DOCKER_HOST').split('/')[-1].split(':')[0])
        container = self.docker.create_container(
                image=build.docker_tag,
                command='/bin/bash -c "nc -l 8080"',
                ports=ports,
                host_config=create_host_config(port_bindings=dict(zip(ports, [None] * len(ports))))
            ).get('Id')
        self.docker.start(container)
        info = self.docker.inspect_container(container)
        port_bindings = {port: bind[0]['HostPort'] for port, bind in info['NetworkSettings']['Ports'].items()}
        for test in tests:
            test.run(host, port_bindings, build.context)
        self.docker.stop(container, timeout=self.STOP_TIMEOUT)
        log_file_path = os.path.join(self.config['global'].get('logs_dir', '/tmp'), '{}.log'.format(build.name))
        with open(log_file_path, 'wb') as logs:
            logs.write(self.docker.logs(container, stdout=True, stderr=True, stream=False))
        print("Container logs wrote to {}".format(log_file_path))
Пример #2
0
class Docker:
    dockerconf = KOOPLEX.get('docker', {})

    def __init__(self):
        base_url = self.dockerconf.get('base_url', '')
        self.client = Client(base_url=base_url)
        logger.debug("Client init")
        self.check = None

    def list_imagenames(self):
        logger.debug("Listing image names")
        pattern_imagenamefilter = KOOPLEX.get('docker', {}).get(
            'pattern_imagename_filter', r'^image-%(\w+):\w$')
        for image in self.client.images(all=True):
            if image['RepoTags'] is None:
                continue
            for tag in image['RepoTags']:
                if re.match(pattern_imagenamefilter, tag):
                    _, imagename, _ = re.split(pattern_imagenamefilter, tag)
                    logger.debug("Found image: %s" % imagename)
                    yield imagename

    def list_volumenames(self):
        logger.debug("Listing volume names")
        volumes = self.client.volumes()
        for volume in volumes['Volumes']:
            yield volume['Name']

    def create_volume(self, volume):
        volume_dir = None  #self.dockerconf.get('volume_dir', '')
        if volume_dir:
            self.client.create_volume(name=volume.name,
                                      driver='local',
                                      driver_opts={
                                          'device':
                                          '%s/%s/' % (volume_dir, volume.name),
                                          'o':
                                          'bind',
                                          'type':
                                          'none'
                                      })
        else:
            self.client.create_volume(name=volume.name, )
        logger.debug("Volume %s created" % volume.name)
        return True  #self.get_container(container)

    def delete_volume(self, volume):
        self.client.remove_volume(name=volume.name)
        logger.debug("Volume %s deleted" % volume.name)

    def get_container(self, container):
        for item in self.client.containers(all=True):
            # docker API prepends '/' in front of container names
            if '/' + container.name in item['Names']:
                logger.debug("Get container %s" % container.name)
                return item
        return None

    def create_container(self, container):
        volumes = []  # the list of mount points in the container
        binds = {}  # a mapping dictionary of the container mounts
        for volume in container.volumes:
            logger.debug("container %s, volume %s" % (container, volume))
            mp = volume.mountpoint
            volumes.append(mp)
            binds[volume.name] = {
                'bind': mp,
                'mode': volume.mode(container.user)
            }
        logger.debug("container %s binds %s" % (container, binds))
        host_config = self.client.create_host_config(
            binds=binds,
            privileged=True,
            mem_limit='2g',
            memswap_limit='170m',
            mem_swappiness=0,
            #            oom_kill_disable = True,
            cpu_shares=2,
        )
        network = self.dockerconf.get('network', 'host')
        networking_config = {'EndpointsConfig': {network: {}}}
        ports = self.dockerconf.get('container_ports', [8000, 9000])
        imagename = container.image.imagename if container.image else self.dockerconf.get(
            'default_image', 'basic')
        args = {
            'name': container.name,
            'image': imagename,
            'detach': True,
            'hostname': container.name,
            'host_config': host_config,
            'networking_config': networking_config,
            'environment': container.environment,
            'volumes': volumes,
            'ports': ports,
        }
        self.client.create_container(**args)
        logger.debug("Container created")
        self.managemount(container)  #FIXME: check if not called twice
        return self.get_container(container)

    def _writefile(self, container_name, path, filename, content):
        import tarfile
        import time
        from io import BytesIO
        tarstream = BytesIO()
        tar = tarfile.TarFile(fileobj=tarstream, mode='w')
        tarinfo = tarfile.TarInfo(name=filename)
        tarinfo.size = len(content)
        tarinfo.mtime = time.time()
        tar.addfile(tarinfo, BytesIO(content))
        tar.close()
        tarstream.seek(0)
        try:
            status = self.client.put_archive(container=container_name,
                                             path=path,
                                             data=tarstream)
            logger.info("container %s put_archive %s/%s returns %s" %
                        (container_name, path, filename, status))
        except Exception as e:
            logger.error("container %s put_archive %s/%s fails -- %s" %
                         (container_name, path, filename, e))

    def managemount(self, container):
        from kooplex.lib.fs_dirname import Dirname

        path, filename = os.path.split(
            self.dockerconf.get('mountconf', '/tmp/mount.conf'))
        mapper = []
        for v in container.volumes:
            mapper.extend([
                "%s:%s" % (v.volumetype, d)
                for d in Dirname.containervolume_listfolders(container, v)
            ])
        #NOTE: mounter uses read to process the mapper configuration, thus we need to make sure '\n' terminates the config mapper file
        mapper.append('')
        logger.debug("container %s map %s" % (container, mapper))
        file_data = "\n".join(mapper).encode('utf8')
        self._writefile(container.name, path, filename, file_data)

    def trigger_impersonator(self, vcproject):  #FIXME: dont call it 1-by-1
        from kooplex.lib.fs_dirname import Dirname
        container_name = self.dockerconf.get('impersonator', 'impersonator')
        path, filename = os.path.split(
            self.dockerconf.get('gitcommandconf', '/tmp/gitcommand.conf'))
        cmdmaps = []
        token = vcproject.token
        fn_clonesh = os.path.join(Dirname.vcpcache(vcproject), "clone.sh")
        fn_key = os.path.join(Dirname.userhome(vcproject.token.user), '.ssh',
                              token.fn_rsa)
        cmdmaps.append(
            "%s:%s:%s:%s" %
            (token.user.username, fn_key, token.repository.domain, fn_clonesh))
        cmdmaps.append('')
        file_data = "\n".join(cmdmaps).encode('utf8')
        self._writefile(container_name, path, filename, file_data)

    def run_container(self, container):
        docker_container_info = self.get_container(container)
        if docker_container_info is None:
            logger.debug("Container did not exist, Creating new one")
            docker_container_info = self.create_container(container)
        container_state = docker_container_info['Status']
        if container_state == 'Created' or container_state.startswith(
                'Exited'):
            logger.debug("Starting container")
            self.start_container(container)

    def refresh_container_state(self, container):
        docker_container_info = self.get_container(container)
        container_state = docker_container_info['State']
        logger.debug("Container state %s" % container_state)
        container.last_message = str(container_state)
        container.last_message_at = now()
        container.save()

    def start_container(self, container):
        self.client.start(container.name)
        # we need to retrieve the container state after starting it
        docker_container_info = self.get_container(container)
        container_state = docker_container_info['State']
        logger.debug("Container state %s" % container_state)
        container.last_message = str(container_state)
        container.last_message_at = now()
        assert container_state == 'running', "Container failed to start: %s" % docker_container_info

    def stop_container(self, container):
        try:
            self.client.stop(container.name)
            container.last_message = 'Container stopped'
        except Exception as e:
            logger.warn("docker container not found by API -- %s" % e)
            container.last_message = str(e)

    def remove_container(self, container):
        try:
            self.client.remove_container(container.name)
            container.last_message = 'Container removed'
            container.last_message_at = now()
        except Exception as e:
            logger.warn("docker container not found by API -- %s" % e)
            container.last_message = str(e)
            container.last_message_at = now()
        logger.debug("Container removed %s" % container.name)

#FIXME: az execute2 lesz az igazi...

    def execute(self, container, command):
        logger.info("execution: %s in %s" % (command, container))
        execution = self.client.exec_create(container=container.name,
                                            cmd=shlex.split(command))
        return self.client.exec_start(execution, detach=False)

    def execute2(self, container, command):
        logger.info("execution: %s in %s" % (command, container))
        execution = self.client.exec_create(container=container.name,
                                            cmd=shlex.split(command))
        response = self.client.exec_start(exec_id=execution['Id'],
                                          stream=False)
        check = self.client.exec_inspect(exec_id=execution['Id'])
        self.check = check
        if check['ExitCode'] != 0:
            logger.error('Execution %s in %s failed -- %s' %
                         (command, container, check))
        return response.decode()
Пример #3
0
class Docker:
    dockerconf = KOOPLEX.get('docker', {})

    def __init__(self):
        base_url = self.dockerconf.get('base_url', '')
        self.client = Client(base_url = base_url)
        logger.debug("Client init")
        self.check = None

    def list_imagenames(self):
        logger.debug("Listing image names")
        pattern_imagenamefilter = KOOPLEX.get('docker', {}).get('pattern_imagename_filter', r'^image-%(\w+):\w$')
        for image in self.client.images(all = True):
            if image['RepoTags'] is None:
                continue
            for tag in image['RepoTags']:
                if re.match(pattern_imagenamefilter, tag):
                    _, imagename, _ = re.split(pattern_imagenamefilter, tag)
                    logger.debug("Found image: %s" % imagename)
                    yield imagename

    def list_volumenames(self):
        logger.debug("Listing volume names")
        volumes = self.client.volumes()
        for volume in volumes['Volumes']:
            yield volume['Name']

    def get_container(self, container):
        for item in self.client.containers(all = True):
            # docker API prepends '/' in front of container names
            if '/' + container.name in item['Names']:
                logger.debug("Get container %s" % container.name)
                return item
        return None

    def create_container(self, container):
        volumes = []    # the list of mount points in the container
        binds = {}      # a mapping dictionary of the container mounts
        for volume in container.volumes:
            logger.debug("container %s, volume %s" % (container, volume))
            mp = volume.mountpoint
            volumes.append(mp)
            binds[volume.name] = { 'bind': mp, 'mode': volume.mode(container.user) }
        logger.debug("container %s binds %s" % (container, binds))
        host_config = self.client.create_host_config(
            binds = binds,
            privileged = True,
            mem_limit = '2g',
            memswap_limit = '170m',
            mem_swappiness = 0,
#            oom_kill_disable = True,
            cpu_shares = 2,
        )
        network = self.dockerconf.get('network', 'host')
        networking_config = { 'EndpointsConfig': { network: {} } }
        ports = self.dockerconf.get('container_ports', [ 8000, 9000 ])
        imagename = container.image.imagename if container.image else self.dockerconf.get('default_image', 'basic')
        args = {
            'name': container.name,
            'image': imagename,
            'detach': True,
            'hostname': container.name,
            'host_config': host_config,
            'networking_config': networking_config,
            'environment': container.environment,
            'volumes': volumes,
            'ports': ports,
        }
        self.client.create_container(**args)
        logger.debug("Container created")
        self.managemount(container) #FIXME: check if not called twice
        return self.get_container(container)

    def _writefile(self, container_name, path, filename, content):
        import tarfile
        import time
        from io import BytesIO
        tarstream = BytesIO()
        tar = tarfile.TarFile(fileobj = tarstream, mode = 'w')
        tarinfo = tarfile.TarInfo(name = filename)
        tarinfo.size = len(content)
        tarinfo.mtime = time.time()
        tar.addfile(tarinfo, BytesIO(content))
        tar.close()
        tarstream.seek(0)
        try:
            status = self.client.put_archive(container = container_name, path = path, data = tarstream)
            logger.info("container %s put_archive %s/%s returns %s" % (container_name, path, filename, status))
        except Exception as e:
            logger.error("container %s put_archive %s/%s fails -- %s" % (container_name, path, filename, e))


    def managemount(self, container):
        from kooplex.lib.fs_dirname import Dirname
        
        path, filename = os.path.split(self.dockerconf.get('mountconf', '/tmp/mount.conf'))
        mapper = []
        for v in container.volumes:
            mapper.extend([ "%s:%s" % (v.volumetype, d) for d in Dirname.containervolume_listfolders(container, v) ])
        #NOTE: mounter uses read to process the mapper configuration, thus we need to make sure '\n' terminates the config mapper file
        mapper.append('')
        logger.debug("container %s map %s" % (container, mapper))
        file_data = "\n".join(mapper).encode('utf8')
        self._writefile(container.name, path, filename, file_data)

    def trigger_impersonator(self, vcproject):       #FIXME: dont call it 1-by-1
        from kooplex.lib.fs_dirname import Dirname
        container_name = self.dockerconf.get('impersonator', 'impersonator')
        path, filename = os.path.split(self.dockerconf.get('gitcommandconf', '/tmp/gitcommand.conf'))
        cmdmaps = []
        token = vcproject.token
        fn_clonesh = os.path.join(Dirname.vcpcache(vcproject), "clone.sh")
        fn_key = os.path.join(Dirname.userhome(vcproject.token.user), '.ssh', token.fn_rsa)
        cmdmaps.append("%s:%s:%s:%s" % (token.user.username, fn_key, token.repository.domain, fn_clonesh))
        cmdmaps.append('')
        file_data = "\n".join(cmdmaps).encode('utf8')
        self._writefile(container_name, path, filename, file_data)


    def run_container(self, container):
        docker_container_info = self.get_container(container)
        if docker_container_info is None:
            logger.debug("Container did not exist, Creating new one")
            docker_container_info = self.create_container(container)
        container_state = docker_container_info['Status']
        if container_state == 'Created' or container_state.startswith('Exited'):
            logger.debug("Starting container")
            self.start_container(container)

    def refresh_container_state(self, container):
        docker_container_info = self.get_container(container)
        container_state = docker_container_info['State']
        logger.debug("Container state %s" % container_state)
        container.last_message = str(container_state)
        container.last_message_at = now()
        container.save()

    def start_container(self, container):
        self.client.start(container.name)
        # we need to retrieve the container state after starting it
        docker_container_info = self.get_container(container)
        container_state = docker_container_info['State']
        logger.debug("Container state %s" % container_state)
        container.last_message = str(container_state)
        container.last_message_at = now()
        assert container_state == 'running', "Container failed to start: %s" % docker_container_info

    def stop_container(self, container):
        try:
            self.client.stop(container.name)
            container.last_message = 'Container stopped'
        except Exception as e:
            logger.warn("docker container not found by API -- %s" % e)
            container.last_message = str(e)

    def remove_container(self, container):
        try:
            self.client.remove_container(container.name)
            container.last_message = 'Container removed'
            container.last_message_at = now()
        except Exception as e:
            logger.warn("docker container not found by API -- %s" % e)
            container.last_message = str(e)
            container.last_message_at = now()
        logger.debug("Container removed %s" % container.name)

#FIXME: az execute2 lesz az igazi...
    def execute(self, container, command):
        logger.info("execution: %s in %s" % (command, container))
        execution = self.client.exec_create(container = container.name, cmd = shlex.split(command))
        return self.client.exec_start(execution, detach = False)

    def execute2(self, container, command):
        logger.info("execution: %s in %s" % (command, container))
        execution = self.client.exec_create(container = container.name, cmd = shlex.split(command))
        response = self.client.exec_start(exec_id = execution['Id'], stream = False)
        check = self.client.exec_inspect(exec_id = execution['Id'])
        self.check = check
        if check['ExitCode'] != 0:
            logger.error('Execution %s in %s failed -- %s' % (command, container, check))
        return response.decode()
Пример #4
0
def delete_instance(docker_api_endpoint, service_name, container_id):
    c = DockerClient(docker_api_endpoint)
    c.stop(container_id)
    c.remove_container(container_id)
Пример #5
0
class DockerPyClient(DockerClient):

    def __init__(self, remote, username=None, password=None, email=None):
        super(DockerPyClient,self).__init__()
        self.client = Client(base_url=remote, version='1.15')
        self.log = logging.getLogger(__name__)
        self.log.debug('password %s, remote = %s, username=%s', password, remote, username)
        if username:
            self.client.login(username=username, password=password, email=email)

    def docker_images(self, filters=None):
        return self.client.images(filters=filters)

    def __id(self, ioc):
        if ioc and 'Id' in ioc:
            return ioc['Id']
        return None
        
    def docker_containers(self):
        return [{
            'Id': cont['Id'],
            'Tag': cont['Image'],
            'Image': self.__id(self.image(cont['Image'])),
            'Names': cont['Names'],
            'Ports': cont['Ports'],
            'Created': cont['Created'],
            'Command': cont['Command'],
            'Status': cont['Status'],
            'Running': cont['Status'].startswith('Up ') or cont['Status'].startswith('Restarting ')
        } for cont in self.client.containers(all=True)]

    def docker_pull(self, image):

        (repository, tag) = self.tag(image)
        existing = self.image(image)

        for line in self.client.pull(repository=repository, stream=True, insecure_registry=True):
            parsed = json.loads(line)
            self.log.debug('parsed %s' % parsed)
            if 'error' in parsed:
                raise Exception(parsed['error'])

        # Check if image updated
        self.flush_images()
        newer = self.image(image)
        if not existing or (newer['Id'] != existing['Id']):
            return True

        return False

    def docker_run(self, entry):

        volumes = ['/var/log/ext']

        kwargs = {
            'image': entry['image'],
            'volumes': volumes,
            'detach': True,
            'environment': {
                'DOCKER_IMAGE': entry['image']
            }
        }

        if 'name' in entry:
            kwargs['name'] = entry['name']

        if 'env' in entry:
            kwargs['environment'].update(entry['env'])

        if 'cpu' in entry:
            kwargs['cpu_shares'] = entry['cpu']

        if 'memory' in entry:
            kwargs['mem_limit'] = entry['memory']

        if 'entrypoint' in entry:
            kwargs['entrypoint'] = entry['entrypoint']

        if 'command' in entry:
            kwargs['command'] = entry['command']

        if 'volumes' in entry:
            volumes.extend([vol['containerPath'] for vol in entry['volumes'] if 'containerPath' in vol])
            volsFrom = [vol['from'] for vol in entry['volumes'] if 'from' in vol]
            if len(volsFrom):
                kwargs['volumes_from'] = volsFrom

        if 'portMappings' in entry:
            kwargs['ports'] = [p['containerPort'] for p in entry['portMappings']]

        container = self.client.create_container(**kwargs)

        self.docker_start(container['Id'], entry)

        return container['Id']

    def docker_start(self, container, entry=None):

        logsBound = False
        binds = {}

        restart_policy = 'on-failure'

        kwargs = {
            'container': container,
            'binds':  binds
        }
        
        if entry is not None:

            if 'network' in entry:
                kwargs['network_mode'] = entry['network']

            if 'privileged' in entry:
                kwargs['privileged'] = entry['privileged']

            if 'volumes' in entry:
                
                volsFrom = []

                for vol in entry['volumes']:

                    if 'from' in vol:
                        volsFrom.append(vol['from'])
                        continue

                    if not 'containerPath' in vol:
                        self.log.warn('No container mount point specified, skipping volume')
                        continue

                    if not 'hostPath' in vol:
                        # Just a local volume, no bindings
                        continue

                    binds[vol['hostPath']] = {
                        'bind': vol['containerPath'],
                        'ro': 'mode' in vol and vol['mode'].lower() == 'ro'
                    }

                    if vol['containerPath'] == '/var/log/ext':
                        logsBound = True

                if len(volsFrom):
                    kwargs['volumes_from'] = volsFrom

            if 'portMappings' in entry:
                portBinds = {}
                for pm in entry['portMappings']:
                    portBinds[pm['containerPort']] = pm['hostPort'] if 'hostPort' in pm else None
                kwargs['port_bindings'] = portBinds

            if 'links' in entry:
                kwargs['links'] = entry['links']

            if 'restart' in entry:
                restart_policy = entry['restart']

        kwargs['restart_policy'] = { 'MaximumRetryCount': 0, 'Name': restart_policy }

        if not logsBound:
            binds['/var/log/ext/%s' % container] = { 'bind': '/var/log/ext', 'ro': False }

        self.client.start(**kwargs);

    def docker_signal(self, container, sig='HUP'):
        self.client.kill(container, sig)

    def docker_restart(self, container):
        self.client.restart(container)

    def docker_stop(self, container):
        self.client.stop(container)

    def docker_rm(self, container):
        self.client.remove_container(container)

    def docker_rmi(self, image):
        # Force removal, sometimes conflicts result from truncated pulls when
        # dockerup container upgrades/dies
        self.client.remove_image(image, force=True)
Пример #6
0
class DockerPyClient(DockerClient):

    def __init__(self, remote, username=None, password=None, email=None):
        super(DockerPyClient,self).__init__()
        self.client = Client(base_url=remote, version='1.15')
        if username:
            self.client.login(username=username, password=password, email=email)

    def docker_images(self, filters=None):
        return self.client.images(filters=filters)

    def __id(self, ioc):
        if ioc and 'Id' in ioc:
            return ioc['Id']
        return None
        
    def docker_containers(self):
        return [{
            'Id': cont['Id'],
            'Tag': cont['Image'],
            'Image': self.__id(self.image(cont['Image'])),
            'Names': cont['Names'],
            'Ports': cont['Ports'],
            'Created': cont['Created'],
            'Command': cont['Command'],
            'Status': cont['Status'],
            'Running': cont['Status'].startswith('Up ') or cont['Status'].startswith('Restarting ')
        } for cont in self.client.containers(all=True)]

    def docker_pull(self, image):

        (repository, tag) = self.tag(image)
        existing = self.image(image)

        for line in self.client.pull(repository=repository, stream=True, insecure_registry=True):
            parsed = json.loads(line)
            if 'error' in parsed:
                raise Exception(parsed['error'])

        # Check if image updated
        self.flush_images()
        newer = self.image(image)
        if not existing or (newer['Id'] != existing['Id']):
            return True

        return False

    def docker_run(self, entry):

        volumes = ['/var/log/ext']

        kwargs = {
            'image': entry['image'],
            'volumes': volumes,
            'detach': True,
            'environment': {
                'DOCKER_IMAGE': entry['image']
            }
        }

        if 'name' in entry:
            kwargs['name'] = entry['name']

        if 'env' in entry:
            kwargs['environment'].update(entry['env'])

        if 'cpu' in entry:
            kwargs['cpu_shares'] = entry['cpu']

        if 'memory' in entry:
            kwargs['mem_limit'] = entry['memory']

        if 'entrypoint' in entry:
            kwargs['entrypoint'] = entry['entrypoint']

        if 'command' in entry:
            kwargs['command'] = entry['command']

        if 'volumes' in entry:
            volumes.extend([vol['containerPath'] for vol in entry['volumes'] if 'containerPath' in vol])
            volsFrom = [vol['from'] for vol in entry['volumes'] if 'from' in vol]
            if len(volsFrom):
                kwargs['volumes_from'] = volsFrom

        if 'portMappings' in entry:
            kwargs['ports'] = [p['containerPort'] for p in entry['portMappings']]

        container = self.client.create_container(**kwargs)

        self.docker_start(container['Id'], entry)

        return container['Id']

    def docker_start(self, container, entry=None):

        logsBound = False
        binds = {}

        restart_policy = 'on-failure'

        kwargs = {
            'container': container,
            'binds':  binds
        }
        
        if entry is not None:

            if 'network' in entry:
                kwargs['network_mode'] = entry['network']

            if 'privileged' in entry:
                kwargs['privileged'] = entry['privileged']

            if 'volumes' in entry:
                
                volsFrom = []

                for vol in entry['volumes']:

                    if 'from' in vol:
                        volsFrom.append(vol['from'])
                        continue

                    if not 'containerPath' in vol:
                        self.log.warn('No container mount point specified, skipping volume')
                        continue

                    if not 'hostPath' in vol:
                        # Just a local volume, no bindings
                        continue

                    binds[vol['hostPath']] = {
                        'bind': vol['containerPath'],
                        'ro': 'mode' in vol and vol['mode'].lower() == 'ro'
                    }

                    if vol['containerPath'] == '/var/log/ext':
                        logsBound = True

                if len(volsFrom):
                    kwargs['volumes_from'] = volsFrom

            if 'portMappings' in entry:
                portBinds = {}
                for pm in entry['portMappings']:
                    portBinds[pm['containerPort']] = pm['hostPort'] if 'hostPort' in pm else None
                kwargs['port_bindings'] = portBinds

            if 'links' in entry:
                kwargs['links'] = entry['links']

            if 'restart' in entry:
                restart_policy = entry['restart']

        kwargs['restart_policy'] = { 'MaximumRetryCount': 0, 'Name': restart_policy }

        if not logsBound:
            binds['/var/log/ext/%s' % container] = { 'bind': '/var/log/ext', 'ro': False }

        self.client.start(**kwargs);

    def docker_signal(self, container, sig='HUP'):
        self.client.kill(container, sig)

    def docker_restart(self, container):
        self.client.restart(container)

    def docker_stop(self, container):
        self.client.stop(container)

    def docker_rm(self, container):
        self.client.remove_container(container)

    def docker_rmi(self, image):
        # Force removal, sometimes conflicts result from truncated pulls when
        # dockerup container upgrades/dies
        self.client.remove_image(image, force=True)