docker-py-1.8.0/0000775000175000017500000000000012702736271012052 5ustar kickkickdocker-py-1.8.0/docker/0000755000175000017500000000000012702731056013313 5ustar kickkickdocker-py-1.8.0/docker/api/0000755000175000017500000000000012702731056014064 5ustar kickkickdocker-py-1.8.0/docker/api/__init__.py0000644000175000017500000000041012702731056016170 0ustar kickkick# flake8: noqa from .build import BuildApiMixin from .container import ContainerApiMixin from .daemon import DaemonApiMixin from .exec_api import ExecApiMixin from .image import ImageApiMixin from .volume import VolumeApiMixin from .network import NetworkApiMixin docker-py-1.8.0/docker/api/build.py0000644000175000017500000001212612702731056015537 0ustar kickkickimport logging import os import re import json from .. import constants from .. import errors from .. import auth from .. import utils log = logging.getLogger(__name__) class BuildApiMixin(object): def build(self, path=None, tag=None, quiet=False, fileobj=None, nocache=False, rm=False, stream=False, timeout=None, custom_context=False, encoding=None, pull=False, forcerm=False, dockerfile=None, container_limits=None, decode=False, buildargs=None, gzip=False): remote = context = headers = None container_limits = container_limits or {} if path is None and fileobj is None: raise TypeError("Either path or fileobj needs to be provided.") if gzip and encoding is not None: raise errors.DockerException( 'Can not use custom encoding if gzip is enabled' ) for key in container_limits.keys(): if key not in constants.CONTAINER_LIMITS_KEYS: raise errors.DockerException( 'Invalid container_limits key {0}'.format(key) ) if custom_context: if not fileobj: raise TypeError("You must specify fileobj with custom_context") context = fileobj elif fileobj is not None: context = utils.mkbuildcontext(fileobj) elif path.startswith(('http://', 'https://', 'git://', 'github.com/', 'git@')): remote = path elif not os.path.isdir(path): raise TypeError("You must specify a directory to build in path") else: dockerignore = os.path.join(path, '.dockerignore') exclude = None if os.path.exists(dockerignore): with open(dockerignore, 'r') as f: exclude = list(filter(bool, f.read().splitlines())) context = utils.tar( path, exclude=exclude, dockerfile=dockerfile, gzip=gzip ) encoding = 'gzip' if gzip else encoding if utils.compare_version('1.8', self._version) >= 0: stream = True if dockerfile and utils.compare_version('1.17', self._version) < 0: raise errors.InvalidVersion( 'dockerfile was only introduced in API version 1.17' ) if utils.compare_version('1.19', self._version) < 0: pull = 1 if pull else 0 u = self._url('/build') params = { 't': tag, 'remote': remote, 'q': quiet, 'nocache': nocache, 'rm': rm, 'forcerm': forcerm, 'pull': pull, 'dockerfile': dockerfile, } params.update(container_limits) if buildargs: if utils.version_gte(self._version, '1.21'): params.update({'buildargs': json.dumps(buildargs)}) else: raise errors.InvalidVersion( 'buildargs was only introduced in API version 1.21' ) if context is not None: headers = {'Content-Type': 'application/tar'} if encoding: headers['Content-Encoding'] = encoding if utils.compare_version('1.9', self._version) >= 0: self._set_auth_headers(headers) response = self._post( u, data=context, params=params, headers=headers, stream=stream, timeout=timeout, ) if context is not None and not custom_context: context.close() if stream: return self._stream_helper(response, decode=decode) else: output = self._result(response) srch = r'Successfully built ([0-9a-f]+)' match = re.search(srch, output) if not match: return None, output return match.group(1), output def _set_auth_headers(self, headers): log.debug('Looking for auth config') # If we don't have any auth data so far, try reloading the config # file one more time in case anything showed up in there. if not self._auth_configs: log.debug("No auth config in memory - loading from filesystem") self._auth_configs = auth.load_config() # Send the full auth configuration (if any exists), since the build # could use any (or all) of the registries. if self._auth_configs: log.debug( 'Sending auth config ({0})'.format( ', '.join(repr(k) for k in self._auth_configs.keys()) ) ) if headers is None: headers = {} if utils.compare_version('1.19', self._version) >= 0: headers['X-Registry-Config'] = auth.encode_header( self._auth_configs ) else: headers['X-Registry-Config'] = auth.encode_header({ 'configs': self._auth_configs }) else: log.debug('No auth config found') docker-py-1.8.0/docker/api/container.py0000644000175000017500000004056012702731056016425 0ustar kickkickimport six import warnings from datetime import datetime from .. import errors from .. import utils from ..utils.utils import create_networking_config, create_endpoint_config class ContainerApiMixin(object): @utils.check_resource def attach(self, container, stdout=True, stderr=True, stream=False, logs=False): params = { 'logs': logs and 1 or 0, 'stdout': stdout and 1 or 0, 'stderr': stderr and 1 or 0, 'stream': stream and 1 or 0, } u = self._url("/containers/{0}/attach", container) response = self._post(u, params=params, stream=stream) return self._get_result(container, stream, response) @utils.check_resource def attach_socket(self, container, params=None, ws=False): if params is None: params = { 'stdout': 1, 'stderr': 1, 'stream': 1 } if ws: return self._attach_websocket(container, params) u = self._url("/containers/{0}/attach", container) return self._get_raw_response_socket(self.post( u, None, params=self._attach_params(params), stream=True)) @utils.check_resource def commit(self, container, repository=None, tag=None, message=None, author=None, changes=None, conf=None): params = { 'container': container, 'repo': repository, 'tag': tag, 'comment': message, 'author': author, 'changes': changes } u = self._url("/commit") return self._result(self._post_json(u, data=conf, params=params), json=True) def containers(self, quiet=False, all=False, trunc=False, latest=False, since=None, before=None, limit=-1, size=False, filters=None): params = { 'limit': 1 if latest else limit, 'all': 1 if all else 0, 'size': 1 if size else 0, 'trunc_cmd': 1 if trunc else 0, 'since': since, 'before': before } if filters: params['filters'] = utils.convert_filters(filters) u = self._url("/containers/json") res = self._result(self._get(u, params=params), True) if quiet: return [{'Id': x['Id']} for x in res] if trunc: for x in res: x['Id'] = x['Id'][:12] return res @utils.check_resource def copy(self, container, resource): if utils.version_gte(self._version, '1.20'): warnings.warn( 'Client.copy() is deprecated for API version >= 1.20, ' 'please use get_archive() instead', DeprecationWarning ) res = self._post_json( self._url("/containers/{0}/copy".format(container)), data={"Resource": resource}, stream=True ) self._raise_for_status(res) return res.raw def create_container(self, image, command=None, hostname=None, user=None, detach=False, stdin_open=False, tty=False, mem_limit=None, ports=None, environment=None, dns=None, volumes=None, volumes_from=None, network_disabled=False, name=None, entrypoint=None, cpu_shares=None, working_dir=None, domainname=None, memswap_limit=None, cpuset=None, host_config=None, mac_address=None, labels=None, volume_driver=None, stop_signal=None, networking_config=None): if isinstance(volumes, six.string_types): volumes = [volumes, ] if host_config and utils.compare_version('1.15', self._version) < 0: raise errors.InvalidVersion( 'host_config is not supported in API < 1.15' ) config = self.create_container_config( image, command, hostname, user, detach, stdin_open, tty, mem_limit, ports, environment, dns, volumes, volumes_from, network_disabled, entrypoint, cpu_shares, working_dir, domainname, memswap_limit, cpuset, host_config, mac_address, labels, volume_driver, stop_signal, networking_config, ) return self.create_container_from_config(config, name) def create_container_config(self, *args, **kwargs): return utils.create_container_config(self._version, *args, **kwargs) def create_container_from_config(self, config, name=None): u = self._url("/containers/create") params = { 'name': name } res = self._post_json(u, data=config, params=params) return self._result(res, True) def create_host_config(self, *args, **kwargs): if not kwargs: kwargs = {} if 'version' in kwargs: raise TypeError( "create_host_config() got an unexpected " "keyword argument 'version'" ) kwargs['version'] = self._version return utils.create_host_config(*args, **kwargs) def create_networking_config(self, *args, **kwargs): return create_networking_config(*args, **kwargs) def create_endpoint_config(self, *args, **kwargs): return create_endpoint_config(self._version, *args, **kwargs) @utils.check_resource def diff(self, container): return self._result( self._get(self._url("/containers/{0}/changes", container)), True ) @utils.check_resource def export(self, container): res = self._get( self._url("/containers/{0}/export", container), stream=True ) self._raise_for_status(res) return res.raw @utils.check_resource @utils.minimum_version('1.20') def get_archive(self, container, path): params = { 'path': path } url = self._url('/containers/{0}/archive', container) res = self._get(url, params=params, stream=True) self._raise_for_status(res) encoded_stat = res.headers.get('x-docker-container-path-stat') return ( res.raw, utils.decode_json_header(encoded_stat) if encoded_stat else None ) @utils.check_resource def inspect_container(self, container): return self._result( self._get(self._url("/containers/{0}/json", container)), True ) @utils.check_resource def kill(self, container, signal=None): url = self._url("/containers/{0}/kill", container) params = {} if signal is not None: params['signal'] = signal res = self._post(url, params=params) self._raise_for_status(res) @utils.check_resource def logs(self, container, stdout=True, stderr=True, stream=False, timestamps=False, tail='all', since=None, follow=None): if utils.compare_version('1.11', self._version) >= 0: if follow is None: follow = stream params = {'stderr': stderr and 1 or 0, 'stdout': stdout and 1 or 0, 'timestamps': timestamps and 1 or 0, 'follow': follow and 1 or 0, } if utils.compare_version('1.13', self._version) >= 0: if tail != 'all' and (not isinstance(tail, int) or tail < 0): tail = 'all' params['tail'] = tail if since is not None: if utils.compare_version('1.19', self._version) < 0: raise errors.InvalidVersion( 'since is not supported in API < 1.19' ) else: if isinstance(since, datetime): params['since'] = utils.datetime_to_timestamp(since) elif (isinstance(since, int) and since > 0): params['since'] = since url = self._url("/containers/{0}/logs", container) res = self._get(url, params=params, stream=stream) return self._get_result(container, stream, res) return self.attach( container, stdout=stdout, stderr=stderr, stream=stream, logs=True ) @utils.check_resource def pause(self, container): url = self._url('/containers/{0}/pause', container) res = self._post(url) self._raise_for_status(res) @utils.check_resource def port(self, container, private_port): res = self._get(self._url("/containers/{0}/json", container)) self._raise_for_status(res) json_ = res.json() private_port = str(private_port) h_ports = None # Port settings is None when the container is running with # network_mode=host. port_settings = json_.get('NetworkSettings', {}).get('Ports') if port_settings is None: return None if '/' in private_port: return port_settings.get(private_port) h_ports = port_settings.get(private_port + '/tcp') if h_ports is None: h_ports = port_settings.get(private_port + '/udp') return h_ports @utils.check_resource @utils.minimum_version('1.20') def put_archive(self, container, path, data): params = {'path': path} url = self._url('/containers/{0}/archive', container) res = self._put(url, params=params, data=data) self._raise_for_status(res) return res.status_code == 200 @utils.check_resource def remove_container(self, container, v=False, link=False, force=False): params = {'v': v, 'link': link, 'force': force} res = self._delete( self._url("/containers/{0}", container), params=params ) self._raise_for_status(res) @utils.minimum_version('1.17') @utils.check_resource def rename(self, container, name): url = self._url("/containers/{0}/rename", container) params = {'name': name} res = self._post(url, params=params) self._raise_for_status(res) @utils.check_resource def resize(self, container, height, width): params = {'h': height, 'w': width} url = self._url("/containers/{0}/resize", container) res = self._post(url, params=params) self._raise_for_status(res) @utils.check_resource def restart(self, container, timeout=10): params = {'t': timeout} url = self._url("/containers/{0}/restart", container) res = self._post(url, params=params) self._raise_for_status(res) @utils.check_resource def start(self, container, binds=None, port_bindings=None, lxc_conf=None, publish_all_ports=None, links=None, privileged=None, dns=None, dns_search=None, volumes_from=None, network_mode=None, restart_policy=None, cap_add=None, cap_drop=None, devices=None, extra_hosts=None, read_only=None, pid_mode=None, ipc_mode=None, security_opt=None, ulimits=None): if utils.compare_version('1.10', self._version) < 0: if dns is not None: raise errors.InvalidVersion( 'dns is only supported for API version >= 1.10' ) if volumes_from is not None: raise errors.InvalidVersion( 'volumes_from is only supported for API version >= 1.10' ) if utils.compare_version('1.15', self._version) < 0: if security_opt is not None: raise errors.InvalidVersion( 'security_opt is only supported for API version >= 1.15' ) if ipc_mode: raise errors.InvalidVersion( 'ipc_mode is only supported for API version >= 1.15' ) if utils.compare_version('1.17', self._version) < 0: if read_only is not None: raise errors.InvalidVersion( 'read_only is only supported for API version >= 1.17' ) if pid_mode is not None: raise errors.InvalidVersion( 'pid_mode is only supported for API version >= 1.17' ) if utils.compare_version('1.18', self._version) < 0: if ulimits is not None: raise errors.InvalidVersion( 'ulimits is only supported for API version >= 1.18' ) start_config_kwargs = dict( binds=binds, port_bindings=port_bindings, lxc_conf=lxc_conf, publish_all_ports=publish_all_ports, links=links, dns=dns, privileged=privileged, dns_search=dns_search, cap_add=cap_add, cap_drop=cap_drop, volumes_from=volumes_from, devices=devices, network_mode=network_mode, restart_policy=restart_policy, extra_hosts=extra_hosts, read_only=read_only, pid_mode=pid_mode, ipc_mode=ipc_mode, security_opt=security_opt, ulimits=ulimits ) start_config = None if any(v is not None for v in start_config_kwargs.values()): if utils.compare_version('1.15', self._version) > 0: warnings.warn( 'Passing host config parameters in start() is deprecated. ' 'Please use host_config in create_container instead!', DeprecationWarning ) start_config = self.create_host_config(**start_config_kwargs) url = self._url("/containers/{0}/start", container) res = self._post_json(url, data=start_config) self._raise_for_status(res) @utils.minimum_version('1.17') @utils.check_resource def stats(self, container, decode=None, stream=True): url = self._url("/containers/{0}/stats", container) if stream: return self._stream_helper(self._get(url, stream=True), decode=decode) else: return self._result(self._get(url, params={'stream': False}), json=True) @utils.check_resource def stop(self, container, timeout=10): params = {'t': timeout} url = self._url("/containers/{0}/stop", container) res = self._post(url, params=params, timeout=(timeout + (self.timeout or 0))) self._raise_for_status(res) @utils.check_resource def top(self, container, ps_args=None): u = self._url("/containers/{0}/top", container) params = {} if ps_args is not None: params['ps_args'] = ps_args return self._result(self._get(u, params=params), True) @utils.check_resource def unpause(self, container): url = self._url('/containers/{0}/unpause', container) res = self._post(url) self._raise_for_status(res) @utils.minimum_version('1.22') @utils.check_resource def update_container( self, container, blkio_weight=None, cpu_period=None, cpu_quota=None, cpu_shares=None, cpuset_cpus=None, cpuset_mems=None, mem_limit=None, mem_reservation=None, memswap_limit=None, kernel_memory=None ): url = self._url('/containers/{0}/update', container) data = {} if blkio_weight: data['BlkioWeight'] = blkio_weight if cpu_period: data['CpuPeriod'] = cpu_period if cpu_shares: data['CpuShares'] = cpu_shares if cpu_quota: data['CpuQuota'] = cpu_quota if cpuset_cpus: data['CpusetCpus'] = cpuset_cpus if cpuset_mems: data['CpusetMems'] = cpuset_mems if mem_limit: data['Memory'] = utils.parse_bytes(mem_limit) if mem_reservation: data['MemoryReservation'] = utils.parse_bytes(mem_reservation) if memswap_limit: data['MemorySwap'] = utils.parse_bytes(memswap_limit) if kernel_memory: data['KernelMemory'] = utils.parse_bytes(kernel_memory) res = self._post_json(url, data=data) return self._result(res, True) @utils.check_resource def wait(self, container, timeout=None): url = self._url("/containers/{0}/wait", container) res = self._post(url, timeout=timeout) self._raise_for_status(res) json_ = res.json() if 'StatusCode' in json_: return json_['StatusCode'] return -1 docker-py-1.8.0/docker/api/daemon.py0000644000175000017500000000522312702731056015703 0ustar kickkickimport os import warnings from datetime import datetime from ..auth import auth from ..constants import INSECURE_REGISTRY_DEPRECATION_WARNING from ..utils import utils class DaemonApiMixin(object): def events(self, since=None, until=None, filters=None, decode=None): if isinstance(since, datetime): since = utils.datetime_to_timestamp(since) if isinstance(until, datetime): until = utils.datetime_to_timestamp(until) if filters: filters = utils.convert_filters(filters) params = { 'since': since, 'until': until, 'filters': filters } return self._stream_helper( self.get(self._url('/events'), params=params, stream=True), decode=decode ) def info(self): return self._result(self._get(self._url("/info")), True) def login(self, username, password=None, email=None, registry=None, reauth=False, insecure_registry=False, dockercfg_path=None): if insecure_registry: warnings.warn( INSECURE_REGISTRY_DEPRECATION_WARNING.format('login()'), DeprecationWarning ) # If we don't have any auth data so far, try reloading the config file # one more time in case anything showed up in there. # If dockercfg_path is passed check to see if the config file exists, # if so load that config. if dockercfg_path and os.path.exists(dockercfg_path): self._auth_configs = auth.load_config(dockercfg_path) elif not self._auth_configs: self._auth_configs = auth.load_config() registry = registry or auth.INDEX_URL authcfg = auth.resolve_authconfig(self._auth_configs, registry) # If we found an existing auth config for this registry and username # combination, we can return it immediately unless reauth is requested. if authcfg and authcfg.get('username', None) == username \ and not reauth: return authcfg req_data = { 'username': username, 'password': password, 'email': email, 'serveraddress': registry, } response = self._post_json(self._url('/auth'), data=req_data) if response.status_code == 200: self._auth_configs[registry] = req_data return self._result(response, json=True) def ping(self): return self._result(self._get(self._url('/_ping'))) def version(self, api_version=True): url = self._url("/version", versioned_api=api_version) return self._result(self._get(url), json=True) docker-py-1.8.0/docker/api/exec_api.py0000644000175000017500000000460612702731056016221 0ustar kickkickimport six from .. import errors from .. import utils class ExecApiMixin(object): @utils.minimum_version('1.15') @utils.check_resource def exec_create(self, container, cmd, stdout=True, stderr=True, stdin=False, tty=False, privileged=False, user=''): if privileged and utils.compare_version('1.19', self._version) < 0: raise errors.InvalidVersion( 'Privileged exec is not supported in API < 1.19' ) if user and utils.compare_version('1.19', self._version) < 0: raise errors.InvalidVersion( 'User-specific exec is not supported in API < 1.19' ) if isinstance(cmd, six.string_types): cmd = utils.split_command(cmd) data = { 'Container': container, 'User': user, 'Privileged': privileged, 'Tty': tty, 'AttachStdin': stdin, 'AttachStdout': stdout, 'AttachStderr': stderr, 'Cmd': cmd } url = self._url('/containers/{0}/exec', container) res = self._post_json(url, data=data) return self._result(res, True) @utils.minimum_version('1.16') def exec_inspect(self, exec_id): if isinstance(exec_id, dict): exec_id = exec_id.get('Id') res = self._get(self._url("/exec/{0}/json", exec_id)) return self._result(res, True) @utils.minimum_version('1.15') def exec_resize(self, exec_id, height=None, width=None): if isinstance(exec_id, dict): exec_id = exec_id.get('Id') params = {'h': height, 'w': width} url = self._url("/exec/{0}/resize", exec_id) res = self._post(url, params=params) self._raise_for_status(res) @utils.minimum_version('1.15') def exec_start(self, exec_id, detach=False, tty=False, stream=False, socket=False): # we want opened socket if socket == True if socket: stream = True if isinstance(exec_id, dict): exec_id = exec_id.get('Id') data = { 'Tty': tty, 'Detach': detach } res = self._post_json( self._url('/exec/{0}/start', exec_id), data=data, stream=stream ) if socket: return self._get_raw_response_socket(res) return self._get_result_tty(stream, res, tty) docker-py-1.8.0/docker/api/image.py0000644000175000017500000002227012702731056015523 0ustar kickkickimport logging import six import warnings from ..auth import auth from ..constants import INSECURE_REGISTRY_DEPRECATION_WARNING from .. import utils from .. import errors log = logging.getLogger(__name__) class ImageApiMixin(object): @utils.check_resource def get_image(self, image): res = self._get(self._url("/images/{0}/get", image), stream=True) self._raise_for_status(res) return res.raw @utils.check_resource def history(self, image): res = self._get(self._url("/images/{0}/history", image)) return self._result(res, True) def images(self, name=None, quiet=False, all=False, viz=False, filters=None): if viz: if utils.compare_version('1.7', self._version) >= 0: raise Exception('Viz output is not supported in API >= 1.7!') return self._result(self._get(self._url("images/viz"))) params = { 'filter': name, 'only_ids': 1 if quiet else 0, 'all': 1 if all else 0, } if filters: params['filters'] = utils.convert_filters(filters) res = self._result(self._get(self._url("/images/json"), params=params), True) if quiet: return [x['Id'] for x in res] return res def import_image(self, src=None, repository=None, tag=None, image=None): if src: if isinstance(src, six.string_types): try: result = self.import_image_from_file( src, repository=repository, tag=tag) except IOError: result = self.import_image_from_url( src, repository=repository, tag=tag) else: result = self.import_image_from_data( src, repository=repository, tag=tag) elif image: result = self.import_image_from_image( image, repository=repository, tag=tag) else: raise Exception("Must specify a src or image") return result def import_image_from_data(self, data, repository=None, tag=None): u = self._url("/images/create") params = { 'fromSrc': '-', 'repo': repository, 'tag': tag } headers = { 'Content-Type': 'application/tar', } return self._result( self._post(u, data=data, params=params, headers=headers)) def import_image_from_file(self, filename, repository=None, tag=None): u = self._url("/images/create") params = { 'fromSrc': '-', 'repo': repository, 'tag': tag } headers = { 'Content-Type': 'application/tar', } with open(filename, 'rb') as f: return self._result( self._post(u, data=f, params=params, headers=headers, timeout=None)) def import_image_from_stream(self, stream, repository=None, tag=None): u = self._url("/images/create") params = { 'fromSrc': '-', 'repo': repository, 'tag': tag } headers = { 'Content-Type': 'application/tar', 'Transfer-Encoding': 'chunked', } return self._result( self._post(u, data=stream, params=params, headers=headers)) def import_image_from_url(self, url, repository=None, tag=None): u = self._url("/images/create") params = { 'fromSrc': url, 'repo': repository, 'tag': tag } return self._result( self._post(u, data=None, params=params)) def import_image_from_image(self, image, repository=None, tag=None): u = self._url("/images/create") params = { 'fromImage': image, 'repo': repository, 'tag': tag } return self._result( self._post(u, data=None, params=params)) @utils.check_resource def insert(self, image, url, path): if utils.compare_version('1.12', self._version) >= 0: raise errors.DeprecatedMethod( 'insert is not available for API version >=1.12' ) api_url = self._url("/images/{0}/insert", image) params = { 'url': url, 'path': path } return self._result(self._post(api_url, params=params)) @utils.check_resource def inspect_image(self, image): return self._result( self._get(self._url("/images/{0}/json", image)), True ) def load_image(self, data): res = self._post(self._url("/images/load"), data=data) self._raise_for_status(res) def pull(self, repository, tag=None, stream=False, insecure_registry=False, auth_config=None, decode=False): if insecure_registry: warnings.warn( INSECURE_REGISTRY_DEPRECATION_WARNING.format('pull()'), DeprecationWarning ) if not tag: repository, tag = utils.parse_repository_tag(repository) registry, repo_name = auth.resolve_repository_name(repository) params = { 'tag': tag, 'fromImage': repository } headers = {} if utils.compare_version('1.5', self._version) >= 0: # If we don't have any auth data so far, try reloading the config # file one more time in case anything showed up in there. if auth_config is None: log.debug('Looking for auth config') if not self._auth_configs: log.debug( "No auth config in memory - loading from filesystem" ) self._auth_configs = auth.load_config() authcfg = auth.resolve_authconfig(self._auth_configs, registry) # Do not fail here if no authentication exists for this # specific registry as we can have a readonly pull. Just # put the header if we can. if authcfg: log.debug('Found auth config') # auth_config needs to be a dict in the format used by # auth.py username , password, serveraddress, email headers['X-Registry-Auth'] = auth.encode_header( authcfg ) else: log.debug('No auth config found') else: log.debug('Sending supplied auth config') headers['X-Registry-Auth'] = auth.encode_header(auth_config) response = self._post( self._url('/images/create'), params=params, headers=headers, stream=stream, timeout=None ) self._raise_for_status(response) if stream: return self._stream_helper(response, decode=decode) return self._result(response) def push(self, repository, tag=None, stream=False, insecure_registry=False, decode=False): if insecure_registry: warnings.warn( INSECURE_REGISTRY_DEPRECATION_WARNING.format('push()'), DeprecationWarning ) if not tag: repository, tag = utils.parse_repository_tag(repository) registry, repo_name = auth.resolve_repository_name(repository) u = self._url("/images/{0}/push", repository) params = { 'tag': tag } headers = {} if utils.compare_version('1.5', self._version) >= 0: # If we don't have any auth data so far, try reloading the config # file one more time in case anything showed up in there. if not self._auth_configs: self._auth_configs = auth.load_config() authcfg = auth.resolve_authconfig(self._auth_configs, registry) # Do not fail here if no authentication exists for this specific # registry as we can have a readonly pull. Just put the header if # we can. if authcfg: headers['X-Registry-Auth'] = auth.encode_header(authcfg) response = self._post_json( u, None, headers=headers, stream=stream, params=params ) self._raise_for_status(response) if stream: return self._stream_helper(response, decode=decode) return self._result(response) @utils.check_resource def remove_image(self, image, force=False, noprune=False): params = {'force': force, 'noprune': noprune} res = self._delete(self._url("/images/{0}", image), params=params) self._raise_for_status(res) def search(self, term): return self._result( self._get(self._url("/images/search"), params={'term': term}), True ) @utils.check_resource def tag(self, image, repository, tag=None, force=False): params = { 'tag': tag, 'repo': repository, 'force': 1 if force else 0 } url = self._url("/images/{0}/tag", image) res = self._post(url, params=params) self._raise_for_status(res) return res.status_code == 201 docker-py-1.8.0/docker/api/network.py0000644000175000017500000000572612702731056016141 0ustar kickkickimport json from ..errors import InvalidVersion from ..utils import check_resource, minimum_version, normalize_links from ..utils import version_lt class NetworkApiMixin(object): @minimum_version('1.21') def networks(self, names=None, ids=None): filters = {} if names: filters['name'] = names if ids: filters['id'] = ids params = {'filters': json.dumps(filters)} url = self._url("/networks") res = self._get(url, params=params) return self._result(res, json=True) @minimum_version('1.21') def create_network(self, name, driver=None, options=None, ipam=None, check_duplicate=None): if options is not None and not isinstance(options, dict): raise TypeError('options must be a dictionary') data = { 'Name': name, 'Driver': driver, 'Options': options, 'IPAM': ipam, 'CheckDuplicate': check_duplicate } url = self._url("/networks/create") res = self._post_json(url, data=data) return self._result(res, json=True) @minimum_version('1.21') def remove_network(self, net_id): url = self._url("/networks/{0}", net_id) res = self._delete(url) self._raise_for_status(res) @minimum_version('1.21') def inspect_network(self, net_id): url = self._url("/networks/{0}", net_id) res = self._get(url) return self._result(res, json=True) @check_resource @minimum_version('1.21') def connect_container_to_network(self, container, net_id, ipv4_address=None, ipv6_address=None, aliases=None, links=None): data = { "Container": container, "EndpointConfig": { "Aliases": aliases, "Links": normalize_links(links) if links else None, }, } # IPv4 or IPv6 or neither: if ipv4_address or ipv6_address: if version_lt(self._version, '1.22'): raise InvalidVersion('IP address assignment is not ' 'supported in API version < 1.22') data['EndpointConfig']['IPAMConfig'] = dict() if ipv4_address: data['EndpointConfig']['IPAMConfig']['IPv4Address'] = \ ipv4_address if ipv6_address: data['EndpointConfig']['IPAMConfig']['IPv6Address'] = \ ipv6_address url = self._url("/networks/{0}/connect", net_id) res = self._post_json(url, data=data) self._raise_for_status(res) @check_resource @minimum_version('1.21') def disconnect_container_from_network(self, container, net_id): data = {"container": container} url = self._url("/networks/{0}/disconnect", net_id) res = self._post_json(url, data=data) self._raise_for_status(res) docker-py-1.8.0/docker/api/volume.py0000644000175000017500000000220612702731056015745 0ustar kickkickfrom .. import utils class VolumeApiMixin(object): @utils.minimum_version('1.21') def volumes(self, filters=None): params = { 'filters': utils.convert_filters(filters) if filters else None } url = self._url('/volumes') return self._result(self._get(url, params=params), True) @utils.minimum_version('1.21') def create_volume(self, name, driver=None, driver_opts=None): url = self._url('/volumes/create') if driver_opts is not None and not isinstance(driver_opts, dict): raise TypeError('driver_opts must be a dictionary') data = { 'Name': name, 'Driver': driver, 'DriverOpts': driver_opts, } return self._result(self._post_json(url, data=data), True) @utils.minimum_version('1.21') def inspect_volume(self, name): url = self._url('/volumes/{0}', name) return self._result(self._get(url), True) @utils.minimum_version('1.21') def remove_volume(self, name): url = self._url('/volumes/{0}', name) resp = self._delete(url) self._raise_for_status(resp) docker-py-1.8.0/docker/auth/0000755000175000017500000000000012702731056014254 5ustar kickkickdocker-py-1.8.0/docker/auth/__init__.py0000644000175000017500000000023512702731056016365 0ustar kickkickfrom .auth import ( INDEX_NAME, INDEX_URL, encode_header, load_config, resolve_authconfig, resolve_repository_name, ) # flake8: noqadocker-py-1.8.0/docker/auth/auth.py0000644000175000017500000001742012702731056015573 0ustar kickkick# Copyright 2013 dotCloud inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import base64 import json import logging import os import six from .. import errors INDEX_NAME = 'docker.io' INDEX_URL = 'https://{0}/v1/'.format(INDEX_NAME) DOCKER_CONFIG_FILENAME = os.path.join('.docker', 'config.json') LEGACY_DOCKER_CONFIG_FILENAME = '.dockercfg' log = logging.getLogger(__name__) def resolve_repository_name(repo_name): if '://' in repo_name: raise errors.InvalidRepository( 'Repository name cannot contain a scheme ({0})'.format(repo_name) ) index_name, remote_name = split_repo_name(repo_name) if index_name[0] == '-' or index_name[-1] == '-': raise errors.InvalidRepository( 'Invalid index name ({0}). Cannot begin or end with a' ' hyphen.'.format(index_name) ) return resolve_index_name(index_name), remote_name def resolve_index_name(index_name): index_name = convert_to_hostname(index_name) if index_name == 'index.' + INDEX_NAME: index_name = INDEX_NAME return index_name def split_repo_name(repo_name): parts = repo_name.split('/', 1) if len(parts) == 1 or ( '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost' ): # This is a docker index repo (ex: username/foobar or ubuntu) return INDEX_NAME, repo_name return tuple(parts) def resolve_authconfig(authconfig, registry=None): """ Returns the authentication data from the given auth configuration for a specific registry. As with the Docker client, legacy entries in the config with full URLs are stripped down to hostnames before checking for a match. Returns None if no match was found. """ # Default to the public index server registry = resolve_index_name(registry) if registry else INDEX_NAME log.debug("Looking for auth entry for {0}".format(repr(registry))) if registry in authconfig: log.debug("Found {0}".format(repr(registry))) return authconfig[registry] for key, config in six.iteritems(authconfig): if resolve_index_name(key) == registry: log.debug("Found {0}".format(repr(key))) return config log.debug("No entry found") return None def convert_to_hostname(url): return url.replace('http://', '').replace('https://', '').split('/', 1)[0] def decode_auth(auth): if isinstance(auth, six.string_types): auth = auth.encode('ascii') s = base64.b64decode(auth) login, pwd = s.split(b':', 1) return login.decode('utf8'), pwd.decode('utf8') def encode_header(auth): auth_json = json.dumps(auth).encode('ascii') return base64.urlsafe_b64encode(auth_json) def parse_auth(entries, raise_on_error=False): """ Parses authentication entries Args: entries: Dict of authentication entries. raise_on_error: If set to true, an invalid format will raise InvalidConfigFile Returns: Authentication registry. """ conf = {} for registry, entry in six.iteritems(entries): if not isinstance(entry, dict): log.debug( 'Config entry for key {0} is not auth config'.format(registry) ) # We sometimes fall back to parsing the whole config as if it was # the auth config by itself, for legacy purposes. In that case, we # fail silently and return an empty conf if any of the keys is not # formatted properly. if raise_on_error: raise errors.InvalidConfigFile( 'Invalid configuration for registry {0}'.format(registry) ) return {} if 'auth' not in entry: # Starting with engine v1.11 (API 1.23), an empty dictionary is # a valid value in the auths config. # https://github.com/docker/compose/issues/3265 log.debug( 'Auth data for {0} is absent. Client might be using a ' 'credentials store instead.' ) return {} username, password = decode_auth(entry['auth']) log.debug( 'Found entry (registry={0}, username={1})' .format(repr(registry), repr(username)) ) conf[registry] = { 'username': username, 'password': password, 'email': entry.get('email'), 'serveraddress': registry, } return conf def find_config_file(config_path=None): environment_path = os.path.join( os.environ.get('DOCKER_CONFIG'), os.path.basename(DOCKER_CONFIG_FILENAME) ) if os.environ.get('DOCKER_CONFIG') else None paths = [ config_path, # 1 environment_path, # 2 os.path.join(os.path.expanduser('~'), DOCKER_CONFIG_FILENAME), # 3 os.path.join( os.path.expanduser('~'), LEGACY_DOCKER_CONFIG_FILENAME ) # 4 ] for path in paths: if path and os.path.exists(path): return path return None def load_config(config_path=None): """ Loads authentication data from a Docker configuration file in the given root directory or if config_path is passed use given path. Lookup priority: explicit config_path parameter > DOCKER_CONFIG environment variable > ~/.docker/config.json > ~/.dockercfg """ config_file = find_config_file(config_path) if not config_file: log.debug("File doesn't exist") return {} try: with open(config_file) as f: data = json.load(f) res = {} if data.get('auths'): log.debug("Found 'auths' section") res.update(parse_auth(data['auths'], raise_on_error=True)) if data.get('HttpHeaders'): log.debug("Found 'HttpHeaders' section") res.update({'HttpHeaders': data['HttpHeaders']}) if data.get('credsStore'): log.debug("Found 'credsStore' section") res.update({'credsStore': data['credsStore']}) if res: return res else: log.debug("Couldn't find 'auths' or 'HttpHeaders' sections") f.seek(0) return parse_auth(json.load(f)) except (IOError, KeyError, ValueError) as e: # Likely missing new Docker config file or it's in an # unknown format, continue to attempt to read old location # and format. log.debug(e) log.debug("Attempting to parse legacy auth file format") try: data = [] with open(config_file) as f: for line in f.readlines(): data.append(line.strip().split(' = ')[1]) if len(data) < 2: # Not enough data raise errors.InvalidConfigFile( 'Invalid or empty configuration file!' ) username, password = decode_auth(data[0]) return { INDEX_NAME: { 'username': username, 'password': password, 'email': data[1], 'serveraddress': INDEX_URL, } } except Exception as e: log.debug(e) pass log.debug("All parsing attempts failed - returning empty config") return {} docker-py-1.8.0/docker/ssladapter/0000755000175000017500000000000012702731056015455 5ustar kickkickdocker-py-1.8.0/docker/ssladapter/__init__.py0000644000175000017500000000006212702731056017564 0ustar kickkickfrom .ssladapter import SSLAdapter # flake8: noqa docker-py-1.8.0/docker/ssladapter/ssladapter.py0000644000175000017500000000445212702731056020176 0ustar kickkick""" Resolves OpenSSL issues in some servers: https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/ https://github.com/kennethreitz/requests/pull/799 """ import sys from distutils.version import StrictVersion from requests.adapters import HTTPAdapter try: import requests.packages.urllib3 as urllib3 except ImportError: import urllib3 PoolManager = urllib3.poolmanager.PoolManager # Monkey-patching match_hostname with a version that supports # IP-address checking. Not necessary for Python 3.5 and above if sys.version_info[0] < 3 or sys.version_info[1] < 5: from .ssl_match_hostname import match_hostname urllib3.connection.match_hostname = match_hostname class SSLAdapter(HTTPAdapter): '''An HTTPS Transport Adapter that uses an arbitrary SSL version.''' def __init__(self, ssl_version=None, assert_hostname=None, assert_fingerprint=None, **kwargs): self.ssl_version = ssl_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint super(SSLAdapter, self).__init__(**kwargs) def init_poolmanager(self, connections, maxsize, block=False): kwargs = { 'num_pools': connections, 'maxsize': maxsize, 'block': block, 'assert_hostname': self.assert_hostname, 'assert_fingerprint': self.assert_fingerprint, } if self.ssl_version and self.can_override_ssl_version(): kwargs['ssl_version'] = self.ssl_version self.poolmanager = PoolManager(**kwargs) def get_connection(self, *args, **kwargs): """ Ensure assert_hostname is set correctly on our pool We already take care of a normal poolmanager via init_poolmanager But we still need to take care of when there is a proxy poolmanager """ conn = super(SSLAdapter, self).get_connection(*args, **kwargs) if conn.assert_hostname != self.assert_hostname: conn.assert_hostname = self.assert_hostname return conn def can_override_ssl_version(self): urllib_ver = urllib3.__version__.split('-')[0] if urllib_ver is None: return False if urllib_ver == 'dev': return True return StrictVersion(urllib_ver) > StrictVersion('1.5') docker-py-1.8.0/docker/ssladapter/ssl_match_hostname.py0000644000175000017500000001106712702731056021707 0ustar kickkick# Slightly modified version of match_hostname in python's ssl library # https://hg.python.org/cpython/file/tip/Lib/ssl.py # Changed to make code python 2.x compatible (unicode strings for ip_address # and 3.5-specific var assignment syntax) import ipaddress import re try: from ssl import CertificateError except ImportError: CertificateError = ValueError import six def _ipaddress_match(ipname, host_ip): """Exact matching of IP addresses. RFC 6125 explicitly doesn't define an algorithm for this (section 1.7.2 - "Out of Scope"). """ # OpenSSL may add a trailing newline to a subjectAltName's IP address ip = ipaddress.ip_address(six.text_type(ipname.rstrip())) return ip == host_ip def _dnsname_match(dn, hostname, max_wildcards=1): """Matching according to RFC 6125, section 6.4.3 http://tools.ietf.org/html/rfc6125#section-6.4.3 """ pats = [] if not dn: return False split_dn = dn.split(r'.') leftmost, remainder = split_dn[0], split_dn[1:] wildcards = leftmost.count('*') if wildcards > max_wildcards: # Issue #17980: avoid denials of service by refusing more # than one wildcard per fragment. A survey of established # policy among SSL implementations showed it to be a # reasonable choice. raise CertificateError( "too many wildcards in certificate DNS name: " + repr(dn)) # speed up common case w/o wildcards if not wildcards: return dn.lower() == hostname.lower() # RFC 6125, section 6.4.3, subitem 1. # The client SHOULD NOT attempt to match a presented identifier in which # the wildcard character comprises a label other than the left-most label. if leftmost == '*': # When '*' is a fragment by itself, it matches a non-empty dotless # fragment. pats.append('[^.]+') elif leftmost.startswith('xn--') or hostname.startswith('xn--'): # RFC 6125, section 6.4.3, subitem 3. # The client SHOULD NOT attempt to match a presented identifier # where the wildcard character is embedded within an A-label or # U-label of an internationalized domain name. pats.append(re.escape(leftmost)) else: # Otherwise, '*' matches any dotless string, e.g. www* pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) # add the remaining fragments, ignore any wildcards for frag in remainder: pats.append(re.escape(frag)) pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) return pat.match(hostname) def match_hostname(cert, hostname): """Verify that *cert* (in decoded format as returned by SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 rules are followed, but IP addresses are not accepted for *hostname*. CertificateError is raised on failure. On success, the function returns nothing. """ if not cert: raise ValueError("empty or no certificate, match_hostname needs a " "SSL socket or SSL context with either " "CERT_OPTIONAL or CERT_REQUIRED") try: host_ip = ipaddress.ip_address(six.text_type(hostname)) except ValueError: # Not an IP address (common case) host_ip = None dnsnames = [] san = cert.get('subjectAltName', ()) for key, value in san: if key == 'DNS': if host_ip is None and _dnsname_match(value, hostname): return dnsnames.append(value) elif key == 'IP Address': if host_ip is not None and _ipaddress_match(value, host_ip): return dnsnames.append(value) if not dnsnames: # The subject is only checked when there is no dNSName entry # in subjectAltName for sub in cert.get('subject', ()): for key, value in sub: # XXX according to RFC 2818, the most specific Common Name # must be used. if key == 'commonName': if _dnsname_match(value, hostname): return dnsnames.append(value) if len(dnsnames) > 1: raise CertificateError( "hostname %r doesn't match either of %s" % (hostname, ', '.join(map(repr, dnsnames)))) elif len(dnsnames) == 1: raise CertificateError( "hostname %r doesn't match %r" % (hostname, dnsnames[0]) ) else: raise CertificateError( "no appropriate commonName or " "subjectAltName fields were found" ) docker-py-1.8.0/docker/unixconn/0000755000175000017500000000000012702734622015156 5ustar kickkickdocker-py-1.8.0/docker/unixconn/__init__.py0000644000175000017500000000006212702731056017263 0ustar kickkickfrom .unixconn import UnixAdapter # flake8: noqa docker-py-1.8.0/docker/unixconn/unixconn.py0000644000175000017500000000616512702734622017401 0ustar kickkick# Copyright 2013 dotCloud inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import six import requests.adapters import socket if six.PY3: import http.client as httplib else: import httplib try: import requests.packages.urllib3 as urllib3 except ImportError: import urllib3 RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer class UnixHTTPConnection(httplib.HTTPConnection, object): def __init__(self, base_url, unix_socket, timeout=60): super(UnixHTTPConnection, self).__init__( 'localhost', timeout=timeout ) self.base_url = base_url self.unix_socket = unix_socket self.timeout = timeout def connect(self): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.settimeout(self.timeout) sock.connect(self.unix_socket) self.sock = sock class UnixHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool): def __init__(self, base_url, socket_path, timeout=60): super(UnixHTTPConnectionPool, self).__init__( 'localhost', timeout=timeout ) self.base_url = base_url self.socket_path = socket_path self.timeout = timeout def _new_conn(self): return UnixHTTPConnection(self.base_url, self.socket_path, self.timeout) class UnixAdapter(requests.adapters.HTTPAdapter): def __init__(self, socket_url, timeout=60): socket_path = socket_url.replace('http+unix://', '') if not socket_path.startswith('/'): socket_path = '/' + socket_path self.socket_path = socket_path self.timeout = timeout self.pools = RecentlyUsedContainer(10, dispose_func=lambda p: p.close()) super(UnixAdapter, self).__init__() def get_connection(self, url, proxies=None): with self.pools.lock: pool = self.pools.get(url) if pool: return pool pool = UnixHTTPConnectionPool( url, self.socket_path, self.timeout ) self.pools[url] = pool return pool def request_url(self, request, proxies): # The select_proxy utility in requests errors out when the provided URL # doesn't have a hostname, like is the case when using a UNIX socket. # Since proxies are an irrelevant notion in the case of UNIX sockets # anyway, we simply return the path URL directly. # See also: https://github.com/docker/docker-py/issues/811 return request.path_url def close(self): self.pools.clear() docker-py-1.8.0/docker/utils/0000755000175000017500000000000012702731056014453 5ustar kickkickdocker-py-1.8.0/docker/utils/ports/0000755000175000017500000000000012702731056015622 5ustar kickkickdocker-py-1.8.0/docker/utils/ports/__init__.py0000644000175000017500000000011612702731056017731 0ustar kickkickfrom .ports import ( split_port, build_port_bindings ) # flake8: noqa docker-py-1.8.0/docker/utils/ports/ports.py0000644000175000017500000000543712702731056017354 0ustar kickkick def add_port_mapping(port_bindings, internal_port, external): if internal_port in port_bindings: port_bindings[internal_port].append(external) else: port_bindings[internal_port] = [external] def add_port(port_bindings, internal_port_range, external_range): if external_range is None: for internal_port in internal_port_range: add_port_mapping(port_bindings, internal_port, None) else: ports = zip(internal_port_range, external_range) for internal_port, external_port in ports: add_port_mapping(port_bindings, internal_port, external_port) def build_port_bindings(ports): port_bindings = {} for port in ports: internal_port_range, external_range = split_port(port) add_port(port_bindings, internal_port_range, external_range) return port_bindings def to_port_range(port): if not port: return None protocol = "" if "/" in port: parts = port.split("/") if len(parts) != 2: _raise_invalid_port(port) port, protocol = parts protocol = "/" + protocol parts = str(port).split('-') if len(parts) == 1: return ["%s%s" % (port, protocol)] if len(parts) == 2: full_port_range = range(int(parts[0]), int(parts[1]) + 1) return ["%s%s" % (p, protocol) for p in full_port_range] raise ValueError('Invalid port range "%s", should be ' 'port or startport-endport' % port) def _raise_invalid_port(port): raise ValueError('Invalid port "%s", should be ' '[[remote_ip:]remote_port[-remote_port]:]' 'port[/protocol]' % port) def split_port(port): parts = str(port).split(':') if not 1 <= len(parts) <= 3: _raise_invalid_port(port) if len(parts) == 1: internal_port, = parts return to_port_range(internal_port), None if len(parts) == 2: external_port, internal_port = parts internal_range = to_port_range(internal_port) external_range = to_port_range(external_port) if internal_range is None or external_range is None: _raise_invalid_port(port) if len(internal_range) != len(external_range): raise ValueError('Port ranges don\'t match in length') return internal_range, external_range external_ip, external_port, internal_port = parts internal_range = to_port_range(internal_port) external_range = to_port_range(external_port) if not external_range: external_range = [None] * len(internal_range) if len(internal_range) != len(external_range): raise ValueError('Port ranges don\'t match in length') return internal_range, [(external_ip, ex_port or None) for ex_port in external_range] docker-py-1.8.0/docker/utils/__init__.py0000644000175000017500000000114112702731056016561 0ustar kickkickfrom .utils import ( compare_version, convert_port_bindings, convert_volume_binds, mkbuildcontext, tar, exclude_paths, parse_repository_tag, parse_host, kwargs_from_env, convert_filters, datetime_to_timestamp, create_host_config, create_container_config, parse_bytes, ping_registry, parse_env_file, version_lt, version_gte, decode_json_header, split_command, create_ipam_config, create_ipam_pool, parse_devices, normalize_links, ) # flake8: noqa from .types import Ulimit, LogConfig # flake8: noqa from .decorators import check_resource, minimum_version, update_headers #flake8: noqa docker-py-1.8.0/docker/utils/decorators.py0000644000175000017500000000276112702731056017200 0ustar kickkickimport functools from .. import errors from . import utils def check_resource(f): @functools.wraps(f) def wrapped(self, resource_id=None, *args, **kwargs): if resource_id is None: if kwargs.get('container'): resource_id = kwargs.pop('container') elif kwargs.get('image'): resource_id = kwargs.pop('image') if isinstance(resource_id, dict): resource_id = resource_id.get('Id') if not resource_id: raise errors.NullResource( 'image or container param is undefined' ) return f(self, resource_id, *args, **kwargs) return wrapped def minimum_version(version): def decorator(f): @functools.wraps(f) def wrapper(self, *args, **kwargs): if utils.version_lt(self._version, version): raise errors.InvalidVersion( '{0} is not available for version < {1}'.format( f.__name__, version ) ) return f(self, *args, **kwargs) return wrapper return decorator def update_headers(f): def inner(self, *args, **kwargs): if 'HttpHeaders' in self._auth_configs: if 'headers' not in kwargs: kwargs['headers'] = self._auth_configs['HttpHeaders'] else: kwargs['headers'].update(self._auth_configs['HttpHeaders']) return f(self, *args, **kwargs) return inner docker-py-1.8.0/docker/utils/utils.py0000644000175000017500000007464412702731056016204 0ustar kickkick# Copyright 2013 dotCloud inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import base64 import io import os import os.path import json import shlex import tarfile import tempfile import warnings from distutils.version import StrictVersion from fnmatch import fnmatch from datetime import datetime import requests import six from .. import constants from .. import errors from .. import tls from .types import Ulimit, LogConfig DEFAULT_HTTP_HOST = "127.0.0.1" DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock" BYTE_UNITS = { 'b': 1, 'k': 1024, 'm': 1024 * 1024, 'g': 1024 * 1024 * 1024 } def create_ipam_pool(subnet=None, iprange=None, gateway=None, aux_addresses=None): return { 'Subnet': subnet, 'IPRange': iprange, 'Gateway': gateway, 'AuxiliaryAddresses': aux_addresses } def create_ipam_config(driver='default', pool_configs=None): return { 'Driver': driver, 'Config': pool_configs or [] } def mkbuildcontext(dockerfile): f = tempfile.NamedTemporaryFile() t = tarfile.open(mode='w', fileobj=f) if isinstance(dockerfile, io.StringIO): dfinfo = tarfile.TarInfo('Dockerfile') if six.PY3: raise TypeError('Please use io.BytesIO to create in-memory ' 'Dockerfiles with Python 3') else: dfinfo.size = len(dockerfile.getvalue()) dockerfile.seek(0) elif isinstance(dockerfile, io.BytesIO): dfinfo = tarfile.TarInfo('Dockerfile') dfinfo.size = len(dockerfile.getvalue()) dockerfile.seek(0) else: dfinfo = t.gettarinfo(fileobj=dockerfile, arcname='Dockerfile') t.addfile(dfinfo, dockerfile) t.close() f.seek(0) return f def decode_json_header(header): data = base64.b64decode(header) if six.PY3: data = data.decode('utf-8') return json.loads(data) def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False): if not fileobj: fileobj = tempfile.NamedTemporaryFile() t = tarfile.open(mode='w:gz' if gzip else 'w', fileobj=fileobj) root = os.path.abspath(path) exclude = exclude or [] for path in sorted(exclude_paths(root, exclude, dockerfile=dockerfile)): t.add(os.path.join(root, path), arcname=path, recursive=False) t.close() fileobj.seek(0) return fileobj def exclude_paths(root, patterns, dockerfile=None): """ Given a root directory path and a list of .dockerignore patterns, return an iterator of all paths (both regular files and directories) in the root directory that do *not* match any of the patterns. All paths returned are relative to the root. """ if dockerfile is None: dockerfile = 'Dockerfile' exceptions = [p for p in patterns if p.startswith('!')] include_patterns = [p[1:] for p in exceptions] include_patterns += [dockerfile, '.dockerignore'] exclude_patterns = list(set(patterns) - set(exceptions)) paths = get_paths(root, exclude_patterns, include_patterns, has_exceptions=len(exceptions) > 0) return set(paths).union( # If the Dockerfile is in a subdirectory that is excluded, get_paths # will not descend into it and the file will be skipped. This ensures # it doesn't happen. set([dockerfile]) if os.path.exists(os.path.join(root, dockerfile)) else set() ) def should_include(path, exclude_patterns, include_patterns): """ Given a path, a list of exclude patterns, and a list of inclusion patterns: 1. Returns True if the path doesn't match any exclusion pattern 2. Returns False if the path matches an exclusion pattern and doesn't match an inclusion pattern 3. Returns true if the path matches an exclusion pattern and matches an inclusion pattern """ for pattern in exclude_patterns: if match_path(path, pattern): for pattern in include_patterns: if match_path(path, pattern): return True return False return True def get_paths(root, exclude_patterns, include_patterns, has_exceptions=False): paths = [] for parent, dirs, files in os.walk(root, topdown=True, followlinks=False): parent = os.path.relpath(parent, root) if parent == '.': parent = '' # If exception rules exist, we can't skip recursing into ignored # directories, as we need to look for exceptions in them. # # It may be possible to optimize this further for exception patterns # that *couldn't* match within ignored directores. # # This matches the current docker logic (as of 2015-11-24): # https://github.com/docker/docker/blob/37ba67bf636b34dc5c0c0265d62a089d0492088f/pkg/archive/archive.go#L555-L557 if not has_exceptions: # Remove excluded patterns from the list of directories to traverse # by mutating the dirs we're iterating over. # This looks strange, but is considered the correct way to skip # traversal. See https://docs.python.org/2/library/os.html#os.walk dirs[:] = [d for d in dirs if should_include(os.path.join(parent, d), exclude_patterns, include_patterns)] for path in dirs: if should_include(os.path.join(parent, path), exclude_patterns, include_patterns): paths.append(os.path.join(parent, path)) for path in files: if should_include(os.path.join(parent, path), exclude_patterns, include_patterns): paths.append(os.path.join(parent, path)) return paths def match_path(path, pattern): pattern = pattern.rstrip('/') pattern_components = pattern.split('/') path_components = path.split('/')[:len(pattern_components)] return fnmatch('/'.join(path_components), pattern) def compare_version(v1, v2): """Compare docker versions >>> v1 = '1.9' >>> v2 = '1.10' >>> compare_version(v1, v2) 1 >>> compare_version(v2, v1) -1 >>> compare_version(v2, v2) 0 """ s1 = StrictVersion(v1) s2 = StrictVersion(v2) if s1 == s2: return 0 elif s1 > s2: return -1 else: return 1 def version_lt(v1, v2): return compare_version(v1, v2) > 0 def version_gte(v1, v2): return not version_lt(v1, v2) def ping_registry(url): warnings.warn( 'The `ping_registry` method is deprecated and will be removed.', DeprecationWarning ) return ping(url + '/v2/', [401]) or ping(url + '/v1/_ping') def ping(url, valid_4xx_statuses=None): try: res = requests.get(url, timeout=3) except Exception: return False else: # We don't send yet auth headers # and a v2 registry will respond with status 401 return ( res.status_code < 400 or (valid_4xx_statuses and res.status_code in valid_4xx_statuses) ) def _convert_port_binding(binding): result = {'HostIp': '', 'HostPort': ''} if isinstance(binding, tuple): if len(binding) == 2: result['HostPort'] = binding[1] result['HostIp'] = binding[0] elif isinstance(binding[0], six.string_types): result['HostIp'] = binding[0] else: result['HostPort'] = binding[0] elif isinstance(binding, dict): if 'HostPort' in binding: result['HostPort'] = binding['HostPort'] if 'HostIp' in binding: result['HostIp'] = binding['HostIp'] else: raise ValueError(binding) else: result['HostPort'] = binding if result['HostPort'] is None: result['HostPort'] = '' else: result['HostPort'] = str(result['HostPort']) return result def convert_port_bindings(port_bindings): result = {} for k, v in six.iteritems(port_bindings): key = str(k) if '/' not in key: key += '/tcp' if isinstance(v, list): result[key] = [_convert_port_binding(binding) for binding in v] else: result[key] = [_convert_port_binding(v)] return result def convert_volume_binds(binds): if isinstance(binds, list): return binds result = [] for k, v in binds.items(): if isinstance(k, six.binary_type): k = k.decode('utf-8') if isinstance(v, dict): if 'ro' in v and 'mode' in v: raise ValueError( 'Binding cannot contain both "ro" and "mode": {}' .format(repr(v)) ) bind = v['bind'] if isinstance(bind, six.binary_type): bind = bind.decode('utf-8') if 'ro' in v: mode = 'ro' if v['ro'] else 'rw' elif 'mode' in v: mode = v['mode'] else: mode = 'rw' result.append( six.text_type('{0}:{1}:{2}').format(k, bind, mode) ) else: if isinstance(v, six.binary_type): v = v.decode('utf-8') result.append( six.text_type('{0}:{1}:rw').format(k, v) ) return result def convert_tmpfs_mounts(tmpfs): if isinstance(tmpfs, dict): return tmpfs if not isinstance(tmpfs, list): raise ValueError( 'Expected tmpfs value to be either a list or a dict, found: {}' .format(type(tmpfs).__name__) ) result = {} for mount in tmpfs: if isinstance(mount, six.string_types): if ":" in mount: name, options = mount.split(":", 1) else: name = mount options = "" else: raise ValueError( "Expected item in tmpfs list to be a string, found: {}" .format(type(mount).__name__) ) result[name] = options return result def parse_repository_tag(repo_name): parts = repo_name.rsplit('@', 1) if len(parts) == 2: return tuple(parts) parts = repo_name.rsplit(':', 1) if len(parts) == 2 and '/' not in parts[1]: return tuple(parts) return repo_name, None # Based on utils.go:ParseHost http://tinyurl.com/nkahcfh # fd:// protocol unsupported (for obvious reasons) # Added support for http and https # Protocol translation: tcp -> http, unix -> http+unix def parse_host(addr, platform=None, tls=False): proto = "http+unix" host = DEFAULT_HTTP_HOST port = None path = '' if not addr and platform == 'win32': addr = '{0}:{1}'.format(DEFAULT_HTTP_HOST, 2375) if not addr or addr.strip() == 'unix://': return DEFAULT_UNIX_SOCKET addr = addr.strip() if addr.startswith('http://'): addr = addr.replace('http://', 'tcp://') if addr.startswith('http+unix://'): addr = addr.replace('http+unix://', 'unix://') if addr == 'tcp://': raise errors.DockerException( "Invalid bind address format: {0}".format(addr) ) elif addr.startswith('unix://'): addr = addr[7:] elif addr.startswith('tcp://'): proto = 'http{0}'.format('s' if tls else '') addr = addr[6:] elif addr.startswith('https://'): proto = "https" addr = addr[8:] elif addr.startswith('fd://'): raise errors.DockerException("fd protocol is not implemented") else: if "://" in addr: raise errors.DockerException( "Invalid bind address protocol: {0}".format(addr) ) proto = "https" if tls else "http" if proto != "http+unix" and ":" in addr: host_parts = addr.split(':') if len(host_parts) != 2: raise errors.DockerException( "Invalid bind address format: {0}".format(addr) ) if host_parts[0]: host = host_parts[0] port = host_parts[1] if '/' in port: port, path = port.split('/', 1) path = '/{0}'.format(path) try: port = int(port) except Exception: raise errors.DockerException( "Invalid port: {0}".format(addr) ) elif proto in ("http", "https") and ':' not in addr: raise errors.DockerException( "Bind address needs a port: {0}".format(addr)) else: host = addr if proto == "http+unix": return "{0}://{1}".format(proto, host) return "{0}://{1}:{2}{3}".format(proto, host, port, path) def parse_devices(devices): device_list = [] for device in devices: if isinstance(device, dict): device_list.append(device) continue if not isinstance(device, six.string_types): raise errors.DockerException( 'Invalid device type {0}'.format(type(device)) ) device_mapping = device.split(':') if device_mapping: path_on_host = device_mapping[0] if len(device_mapping) > 1: path_in_container = device_mapping[1] else: path_in_container = path_on_host if len(device_mapping) > 2: permissions = device_mapping[2] else: permissions = 'rwm' device_list.append({ 'PathOnHost': path_on_host, 'PathInContainer': path_in_container, 'CgroupPermissions': permissions }) return device_list def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None): if not environment: environment = os.environ host = environment.get('DOCKER_HOST') # empty string for cert path is the same as unset. cert_path = environment.get('DOCKER_CERT_PATH') or None # empty string for tls verify counts as "false". # Any value or 'unset' counts as true. tls_verify = environment.get('DOCKER_TLS_VERIFY') if tls_verify == '': tls_verify = False else: tls_verify = tls_verify is not None enable_tls = cert_path or tls_verify params = {} if host: params['base_url'] = ( host.replace('tcp://', 'https://') if enable_tls else host ) if not enable_tls: return params if not cert_path: cert_path = os.path.join(os.path.expanduser('~'), '.docker') if not tls_verify and assert_hostname is None: # assert_hostname is a subset of TLS verification, # so if it's not set already then set it to false. assert_hostname = False params['tls'] = tls.TLSConfig( client_cert=(os.path.join(cert_path, 'cert.pem'), os.path.join(cert_path, 'key.pem')), ca_cert=os.path.join(cert_path, 'ca.pem'), verify=tls_verify, ssl_version=ssl_version, assert_hostname=assert_hostname, ) return params def convert_filters(filters): result = {} for k, v in six.iteritems(filters): if isinstance(v, bool): v = 'true' if v else 'false' if not isinstance(v, list): v = [v, ] result[k] = v return json.dumps(result) def datetime_to_timestamp(dt): """Convert a UTC datetime to a Unix timestamp""" delta = dt - datetime.utcfromtimestamp(0) return delta.seconds + delta.days * 24 * 3600 def longint(n): if six.PY3: return int(n) return long(n) def parse_bytes(s): if isinstance(s, six.integer_types + (float,)): return s if len(s) == 0: return 0 if s[-2:-1].isalpha() and s[-1].isalpha(): if s[-1] == "b" or s[-1] == "B": s = s[:-1] units = BYTE_UNITS suffix = s[-1].lower() # Check if the variable is a string representation of an int # without a units part. Assuming that the units are bytes. if suffix.isdigit(): digits_part = s suffix = 'b' else: digits_part = s[:-1] if suffix in units.keys() or suffix.isdigit(): try: digits = longint(digits_part) except ValueError: raise errors.DockerException( 'Failed converting the string value for memory ({0}) to' ' an integer.'.format(digits_part) ) # Reconvert to long for the final result s = longint(digits * units[suffix]) else: raise errors.DockerException( 'The specified value for memory ({0}) should specify the' ' units. The postfix should be one of the `b` `k` `m` `g`' ' characters'.format(s) ) return s def host_config_type_error(param, param_value, expected): error_msg = 'Invalid type for {0} param: expected {1} but found {2}' return TypeError(error_msg.format(param, expected, type(param_value))) def host_config_version_error(param, version, less_than=True): operator = '<' if less_than else '>' error_msg = '{0} param is not supported in API versions {1} {2}' return errors.InvalidVersion(error_msg.format(param, operator, version)) def host_config_value_error(param, param_value): error_msg = 'Invalid value for {0} param: {1}' return ValueError(error_msg.format(param, param_value)) def create_host_config(binds=None, port_bindings=None, lxc_conf=None, publish_all_ports=False, links=None, privileged=False, dns=None, dns_search=None, volumes_from=None, network_mode=None, restart_policy=None, cap_add=None, cap_drop=None, devices=None, extra_hosts=None, read_only=None, pid_mode=None, ipc_mode=None, security_opt=None, ulimits=None, log_config=None, mem_limit=None, memswap_limit=None, mem_swappiness=None, cgroup_parent=None, group_add=None, cpu_quota=None, cpu_period=None, oom_kill_disable=False, shm_size=None, version=None, tmpfs=None, oom_score_adj=None): host_config = {} if not version: warnings.warn( 'docker.utils.create_host_config() is deprecated. Please use ' 'Client.create_host_config() instead.' ) version = constants.DEFAULT_DOCKER_API_VERSION if mem_limit is not None: host_config['Memory'] = parse_bytes(mem_limit) if memswap_limit is not None: host_config['MemorySwap'] = parse_bytes(memswap_limit) if mem_swappiness is not None: if version_lt(version, '1.20'): raise host_config_version_error('mem_swappiness', '1.20') if not isinstance(mem_swappiness, int): raise host_config_type_error( 'mem_swappiness', mem_swappiness, 'int' ) host_config['MemorySwappiness'] = mem_swappiness if shm_size is not None: if isinstance(shm_size, six.string_types): shm_size = parse_bytes(shm_size) host_config['ShmSize'] = shm_size if pid_mode not in (None, 'host'): raise host_config_value_error('pid_mode', pid_mode) elif pid_mode: host_config['PidMode'] = pid_mode if ipc_mode: host_config['IpcMode'] = ipc_mode if privileged: host_config['Privileged'] = privileged if oom_kill_disable: if version_lt(version, '1.20'): raise host_config_version_error('oom_kill_disable', '1.19') host_config['OomKillDisable'] = oom_kill_disable if oom_score_adj: if version_lt(version, '1.22'): raise host_config_version_error('oom_score_adj', '1.22') if not isinstance(oom_score_adj, int): raise host_config_type_error( 'oom_score_adj', oom_score_adj, 'int' ) host_config['OomScoreAdj'] = oom_score_adj if publish_all_ports: host_config['PublishAllPorts'] = publish_all_ports if read_only is not None: host_config['ReadonlyRootfs'] = read_only if dns_search: host_config['DnsSearch'] = dns_search if network_mode: host_config['NetworkMode'] = network_mode elif network_mode is None and compare_version('1.19', version) > 0: host_config['NetworkMode'] = 'default' if restart_policy: if not isinstance(restart_policy, dict): raise host_config_type_error( 'restart_policy', restart_policy, 'dict' ) host_config['RestartPolicy'] = restart_policy if cap_add: host_config['CapAdd'] = cap_add if cap_drop: host_config['CapDrop'] = cap_drop if devices: host_config['Devices'] = parse_devices(devices) if group_add: if version_lt(version, '1.20'): raise host_config_version_error('group_add', '1.20') host_config['GroupAdd'] = [six.text_type(grp) for grp in group_add] if dns is not None: host_config['Dns'] = dns if security_opt is not None: if not isinstance(security_opt, list): raise host_config_type_error('security_opt', security_opt, 'list') host_config['SecurityOpt'] = security_opt if volumes_from is not None: if isinstance(volumes_from, six.string_types): volumes_from = volumes_from.split(',') host_config['VolumesFrom'] = volumes_from if binds is not None: host_config['Binds'] = convert_volume_binds(binds) if port_bindings is not None: host_config['PortBindings'] = convert_port_bindings(port_bindings) if extra_hosts is not None: if isinstance(extra_hosts, dict): extra_hosts = [ '{0}:{1}'.format(k, v) for k, v in sorted(six.iteritems(extra_hosts)) ] host_config['ExtraHosts'] = extra_hosts if links is not None: host_config['Links'] = normalize_links(links) if isinstance(lxc_conf, dict): formatted = [] for k, v in six.iteritems(lxc_conf): formatted.append({'Key': k, 'Value': str(v)}) lxc_conf = formatted if lxc_conf is not None: host_config['LxcConf'] = lxc_conf if cgroup_parent is not None: host_config['CgroupParent'] = cgroup_parent if ulimits is not None: if not isinstance(ulimits, list): raise host_config_type_error('ulimits', ulimits, 'list') host_config['Ulimits'] = [] for l in ulimits: if not isinstance(l, Ulimit): l = Ulimit(**l) host_config['Ulimits'].append(l) if log_config is not None: if not isinstance(log_config, LogConfig): if not isinstance(log_config, dict): raise host_config_type_error( 'log_config', log_config, 'LogConfig' ) log_config = LogConfig(**log_config) host_config['LogConfig'] = log_config if cpu_quota: if not isinstance(cpu_quota, int): raise host_config_type_error('cpu_quota', cpu_quota, 'int') if version_lt(version, '1.19'): raise host_config_version_error('cpu_quota', '1.19') host_config['CpuQuota'] = cpu_quota if cpu_period: if not isinstance(cpu_period, int): raise host_config_type_error('cpu_period', cpu_period, 'int') if version_lt(version, '1.19'): raise host_config_version_error('cpu_period', '1.19') host_config['CpuPeriod'] = cpu_period if tmpfs: if version_lt(version, '1.22'): raise host_config_version_error('tmpfs', '1.22') host_config["Tmpfs"] = convert_tmpfs_mounts(tmpfs) return host_config def normalize_links(links): if isinstance(links, dict): links = six.iteritems(links) return ['{0}:{1}'.format(k, v) for k, v in sorted(links)] def create_networking_config(endpoints_config=None): networking_config = {} if endpoints_config: networking_config["EndpointsConfig"] = endpoints_config return networking_config def create_endpoint_config(version, aliases=None, links=None): endpoint_config = {} if aliases: if version_lt(version, '1.22'): raise host_config_version_error('endpoint_config.aliases', '1.22') endpoint_config["Aliases"] = aliases if links: if version_lt(version, '1.22'): raise host_config_version_error('endpoint_config.links', '1.22') endpoint_config["Links"] = normalize_links(links) return endpoint_config def parse_env_file(env_file): """ Reads a line-separated environment file. The format of each line should be "key=value". """ environment = {} with open(env_file, 'r') as f: for line in f: if line[0] == '#': continue parse_line = line.strip().split('=') if len(parse_line) == 2: k, v = parse_line environment[k] = v else: raise errors.DockerException( 'Invalid line in environment file {0}:\n{1}'.format( env_file, line)) return environment def split_command(command): if six.PY2 and not isinstance(command, six.binary_type): command = command.encode('utf-8') return shlex.split(command) def format_environment(environment): def format_env(key, value): if value is None: return key return '{key}={value}'.format(key=key, value=value) return [format_env(*var) for var in six.iteritems(environment)] def create_container_config( version, image, command, hostname=None, user=None, detach=False, stdin_open=False, tty=False, mem_limit=None, ports=None, environment=None, dns=None, volumes=None, volumes_from=None, network_disabled=False, entrypoint=None, cpu_shares=None, working_dir=None, domainname=None, memswap_limit=None, cpuset=None, host_config=None, mac_address=None, labels=None, volume_driver=None, stop_signal=None, networking_config=None, ): if isinstance(command, six.string_types): command = split_command(command) if isinstance(entrypoint, six.string_types): entrypoint = split_command(entrypoint) if isinstance(environment, dict): environment = format_environment(environment) if labels is not None and compare_version('1.18', version) < 0: raise errors.InvalidVersion( 'labels were only introduced in API version 1.18' ) if stop_signal is not None and compare_version('1.21', version) < 0: raise errors.InvalidVersion( 'stop_signal was only introduced in API version 1.21' ) if compare_version('1.19', version) < 0: if volume_driver is not None: raise errors.InvalidVersion( 'Volume drivers were only introduced in API version 1.19' ) mem_limit = mem_limit if mem_limit is not None else 0 memswap_limit = memswap_limit if memswap_limit is not None else 0 else: if mem_limit is not None: raise errors.InvalidVersion( 'mem_limit has been moved to host_config in API version 1.19' ) if memswap_limit is not None: raise errors.InvalidVersion( 'memswap_limit has been moved to host_config in API ' 'version 1.19' ) if isinstance(labels, list): labels = dict((lbl, six.text_type('')) for lbl in labels) if mem_limit is not None: mem_limit = parse_bytes(mem_limit) if memswap_limit is not None: memswap_limit = parse_bytes(memswap_limit) if isinstance(ports, list): exposed_ports = {} for port_definition in ports: port = port_definition proto = 'tcp' if isinstance(port_definition, tuple): if len(port_definition) == 2: proto = port_definition[1] port = port_definition[0] exposed_ports['{0}/{1}'.format(port, proto)] = {} ports = exposed_ports if isinstance(volumes, six.string_types): volumes = [volumes, ] if isinstance(volumes, list): volumes_dict = {} for vol in volumes: volumes_dict[vol] = {} volumes = volumes_dict if volumes_from: if not isinstance(volumes_from, six.string_types): volumes_from = ','.join(volumes_from) else: # Force None, an empty list or dict causes client.start to fail volumes_from = None attach_stdin = False attach_stdout = False attach_stderr = False stdin_once = False if not detach: attach_stdout = True attach_stderr = True if stdin_open: attach_stdin = True stdin_once = True if compare_version('1.10', version) >= 0: message = ('{0!r} parameter has no effect on create_container().' ' It has been moved to host_config') if dns is not None: raise errors.InvalidVersion(message.format('dns')) if volumes_from is not None: raise errors.InvalidVersion(message.format('volumes_from')) return { 'Hostname': hostname, 'Domainname': domainname, 'ExposedPorts': ports, 'User': six.text_type(user) if user else None, 'Tty': tty, 'OpenStdin': stdin_open, 'StdinOnce': stdin_once, 'Memory': mem_limit, 'AttachStdin': attach_stdin, 'AttachStdout': attach_stdout, 'AttachStderr': attach_stderr, 'Env': environment, 'Cmd': command, 'Dns': dns, 'Image': image, 'Volumes': volumes, 'VolumesFrom': volumes_from, 'NetworkDisabled': network_disabled, 'Entrypoint': entrypoint, 'CpuShares': cpu_shares, 'Cpuset': cpuset, 'CpusetCpus': cpuset, 'WorkingDir': working_dir, 'MemorySwap': memswap_limit, 'HostConfig': host_config, 'NetworkingConfig': networking_config, 'MacAddress': mac_address, 'Labels': labels, 'VolumeDriver': volume_driver, 'StopSignal': stop_signal } docker-py-1.8.0/docker/utils/types.py0000644000175000017500000000440012702731056016167 0ustar kickkickimport six class LogConfigTypesEnum(object): _values = ( 'json-file', 'syslog', 'journald', 'gelf', 'fluentd', 'none' ) JSON, SYSLOG, JOURNALD, GELF, FLUENTD, NONE = _values class DictType(dict): def __init__(self, init): for k, v in six.iteritems(init): self[k] = v class LogConfig(DictType): types = LogConfigTypesEnum def __init__(self, **kwargs): log_driver_type = kwargs.get('type', kwargs.get('Type')) config = kwargs.get('config', kwargs.get('Config')) or {} if config and not isinstance(config, dict): raise ValueError("LogConfig.config must be a dictionary") super(LogConfig, self).__init__({ 'Type': log_driver_type, 'Config': config }) @property def type(self): return self['Type'] @type.setter def type(self, value): self['Type'] = value @property def config(self): return self['Config'] def set_config_value(self, key, value): self.config[key] = value def unset_config(self, key): if key in self.config: del self.config[key] class Ulimit(DictType): def __init__(self, **kwargs): name = kwargs.get('name', kwargs.get('Name')) soft = kwargs.get('soft', kwargs.get('Soft')) hard = kwargs.get('hard', kwargs.get('Hard')) if not isinstance(name, six.string_types): raise ValueError("Ulimit.name must be a string") if soft and not isinstance(soft, int): raise ValueError("Ulimit.soft must be an integer") if hard and not isinstance(hard, int): raise ValueError("Ulimit.hard must be an integer") super(Ulimit, self).__init__({ 'Name': name, 'Soft': soft, 'Hard': hard }) @property def name(self): return self['Name'] @name.setter def name(self, value): self['Name'] = value @property def soft(self): return self.get('Soft') @soft.setter def soft(self, value): self['Soft'] = value @property def hard(self): return self.get('Hard') @hard.setter def hard(self, value): self['Hard'] = value docker-py-1.8.0/docker/__init__.py0000644000175000017500000000137412702731056015431 0ustar kickkick# Copyright 2013 dotCloud inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from .version import version, version_info __version__ = version __title__ = 'docker-py' from .client import Client, AutoVersionClient, from_env # flake8: noqa docker-py-1.8.0/docker/client.py0000644000175000017500000003035612702731056015152 0ustar kickkick# Copyright 2013 dotCloud inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json import struct import sys import requests import requests.exceptions import six import websocket from . import api from . import constants from . import errors from .auth import auth from .unixconn import unixconn from .ssladapter import ssladapter from .utils import utils, check_resource, update_headers, kwargs_from_env from .tls import TLSConfig def from_env(**kwargs): return Client.from_env(**kwargs) class Client( requests.Session, api.BuildApiMixin, api.ContainerApiMixin, api.DaemonApiMixin, api.ExecApiMixin, api.ImageApiMixin, api.VolumeApiMixin, api.NetworkApiMixin): def __init__(self, base_url=None, version=None, timeout=constants.DEFAULT_TIMEOUT_SECONDS, tls=False): super(Client, self).__init__() if tls and not base_url: raise errors.TLSParameterError( 'If using TLS, the base_url argument must be provided.' ) self.base_url = base_url self.timeout = timeout self._auth_configs = auth.load_config() base_url = utils.parse_host(base_url, sys.platform, tls=bool(tls)) if base_url.startswith('http+unix://'): self._custom_adapter = unixconn.UnixAdapter(base_url, timeout) self.mount('http+docker://', self._custom_adapter) self.base_url = 'http+docker://localunixsocket' else: # Use SSLAdapter for the ability to specify SSL version if isinstance(tls, TLSConfig): tls.configure_client(self) elif tls: self._custom_adapter = ssladapter.SSLAdapter() self.mount('https://', self._custom_adapter) self.base_url = base_url # version detection needs to be after unix adapter mounting if version is None: self._version = constants.DEFAULT_DOCKER_API_VERSION elif isinstance(version, six.string_types): if version.lower() == 'auto': self._version = self._retrieve_server_version() else: self._version = version else: raise errors.DockerException( 'Version parameter must be a string or None. Found {0}'.format( type(version).__name__ ) ) @classmethod def from_env(cls, **kwargs): return cls(**kwargs_from_env(**kwargs)) def _retrieve_server_version(self): try: return self.version(api_version=False)["ApiVersion"] except KeyError: raise errors.DockerException( 'Invalid response from docker daemon: key "ApiVersion"' ' is missing.' ) except Exception as e: raise errors.DockerException( 'Error while fetching server API version: {0}'.format(e) ) def _set_request_timeout(self, kwargs): """Prepare the kwargs for an HTTP request by inserting the timeout parameter, if not already present.""" kwargs.setdefault('timeout', self.timeout) return kwargs @update_headers def _post(self, url, **kwargs): return self.post(url, **self._set_request_timeout(kwargs)) @update_headers def _get(self, url, **kwargs): return self.get(url, **self._set_request_timeout(kwargs)) @update_headers def _put(self, url, **kwargs): return self.put(url, **self._set_request_timeout(kwargs)) @update_headers def _delete(self, url, **kwargs): return self.delete(url, **self._set_request_timeout(kwargs)) def _url(self, pathfmt, *args, **kwargs): for arg in args: if not isinstance(arg, six.string_types): raise ValueError( 'Expected a string but found {0} ({1}) ' 'instead'.format(arg, type(arg)) ) args = map(six.moves.urllib.parse.quote_plus, args) if kwargs.get('versioned_api', True): return '{0}/v{1}{2}'.format( self.base_url, self._version, pathfmt.format(*args) ) else: return '{0}{1}'.format(self.base_url, pathfmt.format(*args)) def _raise_for_status(self, response, explanation=None): """Raises stored :class:`APIError`, if one occurred.""" try: response.raise_for_status() except requests.exceptions.HTTPError as e: if e.response.status_code == 404: raise errors.NotFound(e, response, explanation=explanation) raise errors.APIError(e, response, explanation=explanation) def _result(self, response, json=False, binary=False): assert not (json and binary) self._raise_for_status(response) if json: return response.json() if binary: return response.content return response.text def _post_json(self, url, data, **kwargs): # Go <1.1 can't unserialize null to a string # so we do this disgusting thing here. data2 = {} if data is not None: for k, v in six.iteritems(data): if v is not None: data2[k] = v if 'headers' not in kwargs: kwargs['headers'] = {} kwargs['headers']['Content-Type'] = 'application/json' return self._post(url, data=json.dumps(data2), **kwargs) def _attach_params(self, override=None): return override or { 'stdout': 1, 'stderr': 1, 'stream': 1 } @check_resource def _attach_websocket(self, container, params=None): url = self._url("/containers/{0}/attach/ws", container) req = requests.Request("POST", url, params=self._attach_params(params)) full_url = req.prepare().url full_url = full_url.replace("http://", "ws://", 1) full_url = full_url.replace("https://", "wss://", 1) return self._create_websocket_connection(full_url) def _create_websocket_connection(self, url): return websocket.create_connection(url) def _get_raw_response_socket(self, response): self._raise_for_status(response) if six.PY3: sock = response.raw._fp.fp.raw if self.base_url.startswith("https://"): sock = sock._sock else: sock = response.raw._fp.fp._sock try: # Keep a reference to the response to stop it being garbage # collected. If the response is garbage collected, it will # close TLS sockets. sock._response = response except AttributeError: # UNIX sockets can't have attributes set on them, but that's # fine because we won't be doing TLS over them pass return sock def _stream_helper(self, response, decode=False): """Generator for data coming from a chunked-encoded HTTP response.""" if response.raw._fp.chunked: reader = response.raw while not reader.closed: # this read call will block until we get a chunk data = reader.read(1) if not data: break if reader._fp.chunk_left: data += reader.read(reader._fp.chunk_left) if decode: if six.PY3: data = data.decode('utf-8') data = json.loads(data) yield data else: # Response isn't chunked, meaning we probably # encountered an error immediately yield self._result(response) def _multiplexed_buffer_helper(self, response): """A generator of multiplexed data blocks read from a buffered response.""" buf = self._result(response, binary=True) walker = 0 while True: if len(buf[walker:]) < 8: break _, length = struct.unpack_from('>BxxxL', buf[walker:]) start = walker + constants.STREAM_HEADER_SIZE_BYTES end = start + length walker = end yield buf[start:end] def _multiplexed_response_stream_helper(self, response): """A generator of multiplexed data blocks coming from a response stream.""" # Disable timeout on the underlying socket to prevent # Read timed out(s) for long running processes socket = self._get_raw_response_socket(response) self._disable_socket_timeout(socket) while True: header = response.raw.read(constants.STREAM_HEADER_SIZE_BYTES) if not header: break _, length = struct.unpack('>BxxxL', header) if not length: continue data = response.raw.read(length) if not data: break yield data def _stream_raw_result_old(self, response): ''' Stream raw output for API versions below 1.6 ''' self._raise_for_status(response) for line in response.iter_lines(chunk_size=1, decode_unicode=True): # filter out keep-alive new lines if line: yield line def _stream_raw_result(self, response): ''' Stream result for TTY-enabled container above API 1.6 ''' self._raise_for_status(response) for out in response.iter_content(chunk_size=1, decode_unicode=True): yield out def _disable_socket_timeout(self, socket): """ Depending on the combination of python version and whether we're connecting over http or https, we might need to access _sock, which may or may not exist; or we may need to just settimeout on socket itself, which also may or may not have settimeout on it. To avoid missing the correct one, we try both. """ if hasattr(socket, "settimeout"): socket.settimeout(None) if hasattr(socket, "_sock") and hasattr(socket._sock, "settimeout"): socket._sock.settimeout(None) def _get_result(self, container, stream, res): cont = self.inspect_container(container) return self._get_result_tty(stream, res, cont['Config']['Tty']) def _get_result_tty(self, stream, res, is_tty): # Stream multi-plexing was only introduced in API v1.6. Anything # before that needs old-style streaming. if utils.compare_version('1.6', self._version) < 0: return self._stream_raw_result_old(res) # We should also use raw streaming (without keep-alives) # if we're dealing with a tty-enabled container. if is_tty: return self._stream_raw_result(res) if stream else \ self._result(res, binary=True) self._raise_for_status(res) sep = six.binary_type() if stream: return self._multiplexed_response_stream_helper(res) else: return sep.join( [x for x in self._multiplexed_buffer_helper(res)] ) def get_adapter(self, url): try: return super(Client, self).get_adapter(url) except requests.exceptions.InvalidSchema as e: if self._custom_adapter: return self._custom_adapter else: raise e @property def api_version(self): return self._version class AutoVersionClient(Client): def __init__(self, *args, **kwargs): if 'version' in kwargs and kwargs['version']: raise errors.DockerException( 'Can not specify version for AutoVersionClient' ) kwargs['version'] = 'auto' super(AutoVersionClient, self).__init__(*args, **kwargs) docker-py-1.8.0/docker/constants.py0000644000175000017500000000050212702731056015676 0ustar kickkickDEFAULT_DOCKER_API_VERSION = '1.22' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 CONTAINER_LIMITS_KEYS = [ 'memory', 'memswap', 'cpushares', 'cpusetcpus' ] INSECURE_REGISTRY_DEPRECATION_WARNING = \ 'The `insecure_registry` argument to {} ' \ 'is deprecated and non-functional. Please remove it.' docker-py-1.8.0/docker/errors.py0000644000175000017500000000465512702731056015213 0ustar kickkick# Copyright 2014 dotCloud inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import requests class APIError(requests.exceptions.HTTPError): def __init__(self, message, response, explanation=None): # requests 1.2 supports response as a keyword argument, but # requests 1.1 doesn't super(APIError, self).__init__(message) self.response = response self.explanation = explanation if self.explanation is None and response.content: self.explanation = response.content.strip() def __str__(self): message = super(APIError, self).__str__() if self.is_client_error(): message = '{0} Client Error: {1}'.format( self.response.status_code, self.response.reason) elif self.is_server_error(): message = '{0} Server Error: {1}'.format( self.response.status_code, self.response.reason) if self.explanation: message = '{0} ("{1}")'.format(message, self.explanation) return message def is_client_error(self): return 400 <= self.response.status_code < 500 def is_server_error(self): return 500 <= self.response.status_code < 600 class DockerException(Exception): pass class NotFound(APIError): pass class InvalidVersion(DockerException): pass class InvalidRepository(DockerException): pass class InvalidConfigFile(DockerException): pass class DeprecatedMethod(DockerException): pass class TLSParameterError(DockerException): def __init__(self, msg): self.msg = msg def __str__(self): return self.msg + (". TLS configurations should map the Docker CLI " "client configurations. See " "https://docs.docker.com/engine/articles/https/ " "for API details.") class NullResource(DockerException, ValueError): pass docker-py-1.8.0/docker/tls.py0000644000175000017500000000504312702731056014471 0ustar kickkickimport os import ssl from . import errors from .ssladapter import ssladapter class TLSConfig(object): cert = None ca_cert = None verify = None ssl_version = None def __init__(self, client_cert=None, ca_cert=None, verify=None, ssl_version=None, assert_hostname=None, assert_fingerprint=None): # Argument compatibility/mapping with # https://docs.docker.com/engine/articles/https/ # This diverges from the Docker CLI in that users can specify 'tls' # here, but also disable any public/default CA pool verification by # leaving tls_verify=False self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint # TLS v1.0 seems to be the safest default; SSLv23 fails in mysterious # ways: https://github.com/docker/docker-py/issues/963 self.ssl_version = ssl_version or ssl.PROTOCOL_TLSv1 # "tls" and "tls_verify" must have both or neither cert/key files # In either case, Alert the user when both are expected, but any are # missing. if client_cert: try: tls_cert, tls_key = client_cert except ValueError: raise errors.TLSParameterError( 'client_config must be a tuple of' ' (client certificate, key file)' ) if not (tls_cert and tls_key) or (not os.path.isfile(tls_cert) or not os.path.isfile(tls_key)): raise errors.TLSParameterError( 'Path to a certificate and key files must be provided' ' through the client_config param' ) self.cert = (tls_cert, tls_key) # If verify is set, make sure the cert exists self.verify = verify self.ca_cert = ca_cert if self.verify and self.ca_cert and not os.path.isfile(self.ca_cert): raise errors.TLSParameterError( 'Invalid CA certificate provided for `tls_ca_cert`.' ) def configure_client(self, client): client.ssl_version = self.ssl_version if self.verify and self.ca_cert: client.verify = self.ca_cert else: client.verify = self.verify if self.cert: client.cert = self.cert client.mount('https://', ssladapter.SSLAdapter( ssl_version=self.ssl_version, assert_hostname=self.assert_hostname, assert_fingerprint=self.assert_fingerprint, )) docker-py-1.8.0/docker/version.py0000644000175000017500000000013312702731056015347 0ustar kickkickversion = "1.8.0" version_info = tuple([int(d) for d in version.split("-")[0].split(".")]) docker-py-1.8.0/scripts/0000755000175000017500000000000012702731056013533 5ustar kickkickdocker-py-1.8.0/scripts/release.sh0000755000175000017500000000135512702731056015516 0ustar kickkick#!/bin/bash # # Create the official release # if [ -z "$(command -v pandoc 2> /dev/null)" ]; then >&2 echo "$0 requires http://pandoc.org/" >&2 echo "Please install it and make sure it is available on your \$PATH." exit 2 fi VERSION=$1 REPO=docker/docker-py GITHUB_REPO=git@github.com:$REPO if [ -z $VERSION ]; then echo "Usage: $0 VERSION [upload]" exit 1 fi echo "##> Tagging the release as $VERSION" git tag $VERSION || exit 1 if [[ $2 == 'upload' ]]; then echo "##> Pushing tag to github" git push $GITHUB_REPO $VERSION || exit 1 fi pandoc -f markdown -t rst README.md -o README.rst || exit 1 if [[ $2 == 'upload' ]]; then echo "##> Uploading sdist to pypi" python setup.py sdist bdist_wheel upload fi docker-py-1.8.0/tests/0000755000175000017500000000000012702731056013206 5ustar kickkickdocker-py-1.8.0/tests/integration/0000755000175000017500000000000012702731056015531 5ustar kickkickdocker-py-1.8.0/tests/integration/__init__.py0000644000175000017500000000000012702731056017630 0ustar kickkickdocker-py-1.8.0/tests/integration/api_test.py0000644000175000017500000001404012702731056017712 0ustar kickkickimport base64 import os import tempfile import time import unittest import warnings import docker from .. import helpers class InformationTest(helpers.BaseTestCase): def test_version(self): res = self.client.version() self.assertIn('GoVersion', res) self.assertIn('Version', res) self.assertEqual(len(res['Version'].split('.')), 3) def test_info(self): res = self.client.info() self.assertIn('Containers', res) self.assertIn('Images', res) self.assertIn('Debug', res) def test_search(self): self.client = helpers.docker_client(timeout=10) res = self.client.search('busybox') self.assertTrue(len(res) >= 1) base_img = [x for x in res if x['name'] == 'busybox'] self.assertEqual(len(base_img), 1) self.assertIn('description', base_img[0]) class LinkTest(helpers.BaseTestCase): def test_remove_link(self): # Create containers container1 = self.client.create_container( helpers.BUSYBOX, 'cat', detach=True, stdin_open=True ) container1_id = container1['Id'] self.tmp_containers.append(container1_id) self.client.start(container1_id) # Create Link # we don't want the first / link_path = self.client.inspect_container(container1_id)['Name'][1:] link_alias = 'mylink' container2 = self.client.create_container( helpers.BUSYBOX, 'cat', host_config=self.client.create_host_config( links={link_path: link_alias} ) ) container2_id = container2['Id'] self.tmp_containers.append(container2_id) self.client.start(container2_id) # Remove link linked_name = self.client.inspect_container(container2_id)['Name'][1:] link_name = '%s/%s' % (linked_name, link_alias) self.client.remove_container(link_name, link=True) # Link is gone containers = self.client.containers(all=True) retrieved = [x for x in containers if link_name in x['Names']] self.assertEqual(len(retrieved), 0) # Containers are still there retrieved = [ x for x in containers if x['Id'].startswith(container1_id) or x['Id'].startswith(container2_id) ] self.assertEqual(len(retrieved), 2) class LoadConfigTest(helpers.BaseTestCase): def test_load_legacy_config(self): folder = tempfile.mkdtemp() self.tmp_folders.append(folder) cfg_path = os.path.join(folder, '.dockercfg') f = open(cfg_path, 'w') auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') f.write('auth = {0}\n'.format(auth_)) f.write('email = sakuya@scarlet.net') f.close() cfg = docker.auth.load_config(cfg_path) self.assertNotEqual(cfg[docker.auth.INDEX_NAME], None) cfg = cfg[docker.auth.INDEX_NAME] self.assertEqual(cfg['username'], 'sakuya') self.assertEqual(cfg['password'], 'izayoi') self.assertEqual(cfg['email'], 'sakuya@scarlet.net') self.assertEqual(cfg.get('Auth'), None) def test_load_json_config(self): folder = tempfile.mkdtemp() self.tmp_folders.append(folder) cfg_path = os.path.join(folder, '.dockercfg') f = open(os.path.join(folder, '.dockercfg'), 'w') auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') email_ = 'sakuya@scarlet.net' f.write('{{"{0}": {{"auth": "{1}", "email": "{2}"}}}}\n'.format( docker.auth.INDEX_URL, auth_, email_)) f.close() cfg = docker.auth.load_config(cfg_path) self.assertNotEqual(cfg[docker.auth.INDEX_URL], None) cfg = cfg[docker.auth.INDEX_URL] self.assertEqual(cfg['username'], 'sakuya') self.assertEqual(cfg['password'], 'izayoi') self.assertEqual(cfg['email'], 'sakuya@scarlet.net') self.assertEqual(cfg.get('Auth'), None) class AutoDetectVersionTest(unittest.TestCase): def test_client_init(self): client = helpers.docker_client(version='auto') client_version = client._version api_version = client.version(api_version=False)['ApiVersion'] self.assertEqual(client_version, api_version) api_version_2 = client.version()['ApiVersion'] self.assertEqual(client_version, api_version_2) client.close() def test_auto_client(self): client = docker.AutoVersionClient(**helpers.docker_client_kwargs()) client_version = client._version api_version = client.version(api_version=False)['ApiVersion'] self.assertEqual(client_version, api_version) api_version_2 = client.version()['ApiVersion'] self.assertEqual(client_version, api_version_2) client.close() with self.assertRaises(docker.errors.DockerException): docker.AutoVersionClient( **helpers.docker_client_kwargs(version='1.11') ) class ConnectionTimeoutTest(unittest.TestCase): def setUp(self): self.timeout = 0.5 self.client = docker.client.Client(base_url='http://192.168.10.2:4243', timeout=self.timeout) def test_timeout(self): start = time.time() res = None # This call isn't supposed to complete, and it should fail fast. try: res = self.client.inspect_container('id') except: pass end = time.time() self.assertTrue(res is None) self.assertTrue(end - start < 2 * self.timeout) class UnixconnTest(unittest.TestCase): """ Test UNIX socket connection adapter. """ def test_resource_warnings(self): """ Test no warnings are produced when using the client. """ with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') client = helpers.docker_client() client.images() client.close() del client assert len(w) == 0, \ "No warnings produced: {0}".format(w[0].message) docker-py-1.8.0/tests/integration/build_test.py0000644000175000017500000001225712702731056020250 0ustar kickkickimport io import json import os import shutil import tempfile import six from docker import errors from .. import helpers from ..base import requires_api_version class BuildTest(helpers.BaseTestCase): def test_build_streaming(self): script = io.BytesIO('\n'.join([ 'FROM busybox', 'MAINTAINER docker-py', 'RUN mkdir -p /tmp/test', 'EXPOSE 8080', 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' ' /tmp/silence.tar.gz' ]).encode('ascii')) stream = self.client.build(fileobj=script, stream=True) logs = '' for chunk in stream: if six.PY3: chunk = chunk.decode('utf-8') json.loads(chunk) # ensure chunk is a single, valid JSON blob logs += chunk self.assertNotEqual(logs, '') def test_build_from_stringio(self): if six.PY3: return script = io.StringIO(six.text_type('\n').join([ 'FROM busybox', 'MAINTAINER docker-py', 'RUN mkdir -p /tmp/test', 'EXPOSE 8080', 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' ' /tmp/silence.tar.gz' ])) stream = self.client.build(fileobj=script, stream=True) logs = '' for chunk in stream: if six.PY3: chunk = chunk.decode('utf-8') logs += chunk self.assertNotEqual(logs, '') @requires_api_version('1.8') def test_build_with_dockerignore(self): base_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, base_dir) with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f: f.write("\n".join([ 'FROM busybox', 'MAINTAINER docker-py', 'ADD . /test', ])) with open(os.path.join(base_dir, '.dockerignore'), 'w') as f: f.write("\n".join([ 'ignored', 'Dockerfile', '.dockerignore', '!ignored/subdir/excepted-file', '', # empty line ])) with open(os.path.join(base_dir, 'not-ignored'), 'w') as f: f.write("this file should not be ignored") subdir = os.path.join(base_dir, 'ignored', 'subdir') os.makedirs(subdir) with open(os.path.join(subdir, 'file'), 'w') as f: f.write("this file should be ignored") with open(os.path.join(subdir, 'excepted-file'), 'w') as f: f.write("this file should not be ignored") tag = 'docker-py-test-build-with-dockerignore' stream = self.client.build( path=base_dir, tag=tag, ) for chunk in stream: pass c = self.client.create_container(tag, ['find', '/test', '-type', 'f']) self.client.start(c) self.client.wait(c) logs = self.client.logs(c) if six.PY3: logs = logs.decode('utf-8') self.assertEqual( sorted(list(filter(None, logs.split('\n')))), sorted(['/test/ignored/subdir/excepted-file', '/test/not-ignored']), ) @requires_api_version('1.21') def test_build_with_buildargs(self): script = io.BytesIO('\n'.join([ 'FROM scratch', 'ARG test', 'USER $test' ]).encode('ascii')) stream = self.client.build( fileobj=script, tag='buildargs', buildargs={'test': 'OK'} ) self.tmp_imgs.append('buildargs') for chunk in stream: pass info = self.client.inspect_image('buildargs') self.assertEqual(info['Config']['User'], 'OK') def test_build_stderr_data(self): control_chars = ['\x1b[91m', '\x1b[0m'] snippet = 'Ancient Temple (Mystic Oriental Dream ~ Ancient Temple)' script = io.BytesIO(b'\n'.join([ b'FROM busybox', 'RUN sh -c ">&2 echo \'{0}\'"'.format(snippet).encode('utf-8') ])) stream = self.client.build( fileobj=script, stream=True, decode=True, nocache=True ) lines = [] for chunk in stream: lines.append(chunk.get('stream')) expected = '{0}{2}\n{1}'.format( control_chars[0], control_chars[1], snippet ) self.assertTrue(any([line == expected for line in lines])) def test_build_gzip_encoding(self): base_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, base_dir) with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f: f.write("\n".join([ 'FROM busybox', 'MAINTAINER docker-py', 'ADD . /test', ])) stream = self.client.build( path=base_dir, stream=True, decode=True, nocache=True, gzip=True ) lines = [] for chunk in stream: lines.append(chunk) assert 'Successfully built' in lines[-1]['stream'] def test_build_gzip_custom_encoding(self): with self.assertRaises(errors.DockerException): self.client.build(path='.', gzip=True, encoding='text/html') docker-py-1.8.0/tests/integration/conftest.py0000644000175000017500000000150612702731056017732 0ustar kickkickfrom __future__ import print_function import json import sys import warnings import docker.errors import pytest from ..helpers import BUSYBOX from ..helpers import docker_client @pytest.fixture(autouse=True, scope='session') def setup_test_session(): warnings.simplefilter('error') c = docker_client() try: c.inspect_image(BUSYBOX) except docker.errors.NotFound: print("\npulling {0}".format(BUSYBOX), file=sys.stderr) for data in c.pull(BUSYBOX, stream=True): data = json.loads(data.decode('utf-8')) status = data.get("status") progress = data.get("progress") detail = "{0} - {1}".format(status, progress) print(detail, file=sys.stderr) # Double make sure we now have busybox c.inspect_image(BUSYBOX) c.close() docker-py-1.8.0/tests/integration/container_test.py0000644000175000017500000011655312702731056021137 0ustar kickkickimport os import signal import tempfile import docker import pytest import six from ..base import requires_api_version from .. import helpers BUSYBOX = helpers.BUSYBOX class ListContainersTest(helpers.BaseTestCase): def test_list_containers(self): res0 = self.client.containers(all=True) size = len(res0) res1 = self.client.create_container(BUSYBOX, 'true') self.assertIn('Id', res1) self.client.start(res1['Id']) self.tmp_containers.append(res1['Id']) res2 = self.client.containers(all=True) self.assertEqual(size + 1, len(res2)) retrieved = [x for x in res2 if x['Id'].startswith(res1['Id'])] self.assertEqual(len(retrieved), 1) retrieved = retrieved[0] self.assertIn('Command', retrieved) self.assertEqual(retrieved['Command'], six.text_type('true')) self.assertIn('Image', retrieved) self.assertRegex(retrieved['Image'], r'busybox:.*') self.assertIn('Status', retrieved) class CreateContainerTest(helpers.BaseTestCase): def test_create(self): res = self.client.create_container(BUSYBOX, 'true') self.assertIn('Id', res) self.tmp_containers.append(res['Id']) def test_create_with_host_pid_mode(self): ctnr = self.client.create_container( BUSYBOX, 'true', host_config=self.client.create_host_config( pid_mode='host', network_mode='none' ) ) self.assertIn('Id', ctnr) self.tmp_containers.append(ctnr['Id']) self.client.start(ctnr) inspect = self.client.inspect_container(ctnr) self.assertIn('HostConfig', inspect) host_config = inspect['HostConfig'] self.assertIn('PidMode', host_config) self.assertEqual(host_config['PidMode'], 'host') def test_create_with_links(self): res0 = self.client.create_container( BUSYBOX, 'cat', detach=True, stdin_open=True, environment={'FOO': '1'}) container1_id = res0['Id'] self.tmp_containers.append(container1_id) self.client.start(container1_id) res1 = self.client.create_container( BUSYBOX, 'cat', detach=True, stdin_open=True, environment={'FOO': '1'}) container2_id = res1['Id'] self.tmp_containers.append(container2_id) self.client.start(container2_id) # we don't want the first / link_path1 = self.client.inspect_container(container1_id)['Name'][1:] link_alias1 = 'mylink1' link_env_prefix1 = link_alias1.upper() link_path2 = self.client.inspect_container(container2_id)['Name'][1:] link_alias2 = 'mylink2' link_env_prefix2 = link_alias2.upper() res2 = self.client.create_container( BUSYBOX, 'env', host_config=self.client.create_host_config( links={link_path1: link_alias1, link_path2: link_alias2}, network_mode='bridge' ) ) container3_id = res2['Id'] self.tmp_containers.append(container3_id) self.client.start(container3_id) self.assertEqual(self.client.wait(container3_id), 0) logs = self.client.logs(container3_id) if six.PY3: logs = logs.decode('utf-8') self.assertIn('{0}_NAME='.format(link_env_prefix1), logs) self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix1), logs) self.assertIn('{0}_NAME='.format(link_env_prefix2), logs) self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix2), logs) def test_create_with_restart_policy(self): container = self.client.create_container( BUSYBOX, ['sleep', '2'], host_config=self.client.create_host_config( restart_policy={"Name": "always", "MaximumRetryCount": 0}, network_mode='none' ) ) id = container['Id'] self.client.start(id) self.client.wait(id) with self.assertRaises(docker.errors.APIError) as exc: self.client.remove_container(id) err = exc.exception.response.text self.assertIn( 'You cannot remove a running container', err ) self.client.remove_container(id, force=True) def test_create_container_with_volumes_from(self): vol_names = ['foobar_vol0', 'foobar_vol1'] res0 = self.client.create_container( BUSYBOX, 'true', name=vol_names[0] ) container1_id = res0['Id'] self.tmp_containers.append(container1_id) self.client.start(container1_id) res1 = self.client.create_container( BUSYBOX, 'true', name=vol_names[1] ) container2_id = res1['Id'] self.tmp_containers.append(container2_id) self.client.start(container2_id) with self.assertRaises(docker.errors.DockerException): self.client.create_container( BUSYBOX, 'cat', detach=True, stdin_open=True, volumes_from=vol_names ) res2 = self.client.create_container( BUSYBOX, 'cat', detach=True, stdin_open=True, host_config=self.client.create_host_config( volumes_from=vol_names, network_mode='none' ) ) container3_id = res2['Id'] self.tmp_containers.append(container3_id) self.client.start(container3_id) info = self.client.inspect_container(res2['Id']) self.assertCountEqual(info['HostConfig']['VolumesFrom'], vol_names) def create_container_readonly_fs(self): if not helpers.exec_driver_is_native(): pytest.skip('Exec driver not native') ctnr = self.client.create_container( BUSYBOX, ['mkdir', '/shrine'], host_config=self.client.create_host_config( read_only=True, network_mode='none' ) ) self.assertIn('Id', ctnr) self.tmp_containers.append(ctnr['Id']) self.client.start(ctnr) res = self.client.wait(ctnr) self.assertNotEqual(res, 0) def create_container_with_name(self): res = self.client.create_container(BUSYBOX, 'true', name='foobar') self.assertIn('Id', res) self.tmp_containers.append(res['Id']) inspect = self.client.inspect_container(res['Id']) self.assertIn('Name', inspect) self.assertEqual('/foobar', inspect['Name']) def create_container_privileged(self): res = self.client.create_container( BUSYBOX, 'true', host_config=self.client.create_host_config( privileged=True, network_mode='none' ) ) self.assertIn('Id', res) self.tmp_containers.append(res['Id']) self.client.start(res['Id']) inspect = self.client.inspect_container(res['Id']) self.assertIn('Config', inspect) self.assertIn('Id', inspect) self.assertTrue(inspect['Id'].startswith(res['Id'])) self.assertIn('Image', inspect) self.assertIn('State', inspect) self.assertIn('Running', inspect['State']) if not inspect['State']['Running']: self.assertIn('ExitCode', inspect['State']) self.assertEqual(inspect['State']['ExitCode'], 0) # Since Nov 2013, the Privileged flag is no longer part of the # container's config exposed via the API (safety concerns?). # if 'Privileged' in inspect['Config']: self.assertEqual(inspect['Config']['Privileged'], True) def test_create_with_mac_address(self): mac_address_expected = "02:42:ac:11:00:0a" container = self.client.create_container( BUSYBOX, ['sleep', '60'], mac_address=mac_address_expected) id = container['Id'] self.client.start(container) res = self.client.inspect_container(container['Id']) self.assertEqual(mac_address_expected, res['NetworkSettings']['MacAddress']) self.client.kill(id) @requires_api_version('1.20') def test_group_id_ints(self): container = self.client.create_container( BUSYBOX, 'id -G', host_config=self.client.create_host_config(group_add=[1000, 1001]) ) self.tmp_containers.append(container) self.client.start(container) self.client.wait(container) logs = self.client.logs(container) if six.PY3: logs = logs.decode('utf-8') groups = logs.strip().split(' ') self.assertIn('1000', groups) self.assertIn('1001', groups) @requires_api_version('1.20') def test_group_id_strings(self): container = self.client.create_container( BUSYBOX, 'id -G', host_config=self.client.create_host_config( group_add=['1000', '1001'] ) ) self.tmp_containers.append(container) self.client.start(container) self.client.wait(container) logs = self.client.logs(container) if six.PY3: logs = logs.decode('utf-8') groups = logs.strip().split(' ') self.assertIn('1000', groups) self.assertIn('1001', groups) def test_valid_log_driver_and_log_opt(self): log_config = docker.utils.LogConfig( type='json-file', config={'max-file': '100'} ) container = self.client.create_container( BUSYBOX, ['true'], host_config=self.client.create_host_config(log_config=log_config) ) self.tmp_containers.append(container['Id']) self.client.start(container) info = self.client.inspect_container(container) container_log_config = info['HostConfig']['LogConfig'] self.assertEqual(container_log_config['Type'], log_config.type) self.assertEqual(container_log_config['Config'], log_config.config) def test_invalid_log_driver_raises_exception(self): log_config = docker.utils.LogConfig( type='asdf-nope', config={} ) expected_msg = "logger: no log driver named 'asdf-nope' is registered" with pytest.raises(docker.errors.APIError) as excinfo: # raises an internal server error 500 container = self.client.create_container( BUSYBOX, ['true'], host_config=self.client.create_host_config( log_config=log_config ) ) self.client.start(container) assert expected_msg in str(excinfo.value) def test_valid_no_log_driver_specified(self): log_config = docker.utils.LogConfig( type="", config={'max-file': '100'} ) container = self.client.create_container( BUSYBOX, ['true'], host_config=self.client.create_host_config(log_config=log_config) ) self.tmp_containers.append(container['Id']) self.client.start(container) info = self.client.inspect_container(container) container_log_config = info['HostConfig']['LogConfig'] self.assertEqual(container_log_config['Type'], "json-file") self.assertEqual(container_log_config['Config'], log_config.config) def test_valid_no_config_specified(self): log_config = docker.utils.LogConfig( type="json-file", config=None ) container = self.client.create_container( BUSYBOX, ['true'], host_config=self.client.create_host_config(log_config=log_config) ) self.tmp_containers.append(container['Id']) self.client.start(container) info = self.client.inspect_container(container) container_log_config = info['HostConfig']['LogConfig'] self.assertEqual(container_log_config['Type'], "json-file") self.assertEqual(container_log_config['Config'], {}) def test_create_with_memory_constraints_with_str(self): ctnr = self.client.create_container( BUSYBOX, 'true', host_config=self.client.create_host_config( memswap_limit='1G', mem_limit='700M' ) ) self.assertIn('Id', ctnr) self.tmp_containers.append(ctnr['Id']) self.client.start(ctnr) inspect = self.client.inspect_container(ctnr) self.assertIn('HostConfig', inspect) host_config = inspect['HostConfig'] for limit in ['Memory', 'MemorySwap']: self.assertIn(limit, host_config) def test_create_with_memory_constraints_with_int(self): ctnr = self.client.create_container( BUSYBOX, 'true', host_config=self.client.create_host_config(mem_swappiness=40) ) self.assertIn('Id', ctnr) self.tmp_containers.append(ctnr['Id']) self.client.start(ctnr) inspect = self.client.inspect_container(ctnr) self.assertIn('HostConfig', inspect) host_config = inspect['HostConfig'] self.assertIn('MemorySwappiness', host_config) def test_create_host_config_exception_raising(self): self.assertRaises(TypeError, self.client.create_host_config, mem_swappiness='40') self.assertRaises(ValueError, self.client.create_host_config, pid_mode='40') def test_create_with_environment_variable_no_value(self): container = self.client.create_container( BUSYBOX, ['echo'], environment={'Foo': None, 'Other': 'one', 'Blank': ''}, ) self.tmp_containers.append(container['Id']) config = self.client.inspect_container(container['Id']) assert ( sorted(config['Config']['Env']) == sorted(['Foo', 'Other=one', 'Blank=']) ) @requires_api_version('1.22') def test_create_with_tmpfs(self): tmpfs = { '/tmp1': 'size=3M' } container = self.client.create_container( BUSYBOX, ['echo'], host_config=self.client.create_host_config( tmpfs=tmpfs)) self.tmp_containers.append(container['Id']) config = self.client.inspect_container(container) assert config['HostConfig']['Tmpfs'] == tmpfs class VolumeBindTest(helpers.BaseTestCase): def setUp(self): super(VolumeBindTest, self).setUp() self.mount_dest = '/mnt' # Get a random pathname - we don't need it to exist locally self.mount_origin = tempfile.mkdtemp() self.filename = 'shared.txt' self.run_with_volume( False, BUSYBOX, ['touch', os.path.join(self.mount_dest, self.filename)], ) def test_create_with_binds_rw(self): container = self.run_with_volume( False, BUSYBOX, ['ls', self.mount_dest], ) logs = self.client.logs(container) if six.PY3: logs = logs.decode('utf-8') self.assertIn(self.filename, logs) inspect_data = self.client.inspect_container(container) self.check_container_data(inspect_data, True) def test_create_with_binds_ro(self): self.run_with_volume( False, BUSYBOX, ['touch', os.path.join(self.mount_dest, self.filename)], ) container = self.run_with_volume( True, BUSYBOX, ['ls', self.mount_dest], ) logs = self.client.logs(container) if six.PY3: logs = logs.decode('utf-8') self.assertIn(self.filename, logs) inspect_data = self.client.inspect_container(container) self.check_container_data(inspect_data, False) def check_container_data(self, inspect_data, rw): if docker.utils.compare_version('1.20', self.client._version) < 0: self.assertIn('Volumes', inspect_data) self.assertIn(self.mount_dest, inspect_data['Volumes']) self.assertEqual( self.mount_origin, inspect_data['Volumes'][self.mount_dest] ) self.assertIn(self.mount_dest, inspect_data['VolumesRW']) self.assertFalse(inspect_data['VolumesRW'][self.mount_dest]) else: self.assertIn('Mounts', inspect_data) filtered = list(filter( lambda x: x['Destination'] == self.mount_dest, inspect_data['Mounts'] )) self.assertEqual(len(filtered), 1) mount_data = filtered[0] self.assertEqual(mount_data['Source'], self.mount_origin) self.assertEqual(mount_data['RW'], rw) def run_with_volume(self, ro, *args, **kwargs): return self.run_container( *args, volumes={self.mount_dest: {}}, host_config=self.client.create_host_config( binds={ self.mount_origin: { 'bind': self.mount_dest, 'ro': ro, }, }, network_mode='none' ), **kwargs ) @requires_api_version('1.20') class ArchiveTest(helpers.BaseTestCase): def test_get_file_archive_from_container(self): data = 'The Maid and the Pocket Watch of Blood' ctnr = self.client.create_container( BUSYBOX, 'sh -c "echo {0} > /vol1/data.txt"'.format(data), volumes=['/vol1'] ) self.tmp_containers.append(ctnr) self.client.start(ctnr) self.client.wait(ctnr) with tempfile.NamedTemporaryFile() as destination: strm, stat = self.client.get_archive(ctnr, '/vol1/data.txt') for d in strm: destination.write(d) destination.seek(0) retrieved_data = helpers.untar_file(destination, 'data.txt') if six.PY3: retrieved_data = retrieved_data.decode('utf-8') self.assertEqual(data, retrieved_data.strip()) def test_get_file_stat_from_container(self): data = 'The Maid and the Pocket Watch of Blood' ctnr = self.client.create_container( BUSYBOX, 'sh -c "echo -n {0} > /vol1/data.txt"'.format(data), volumes=['/vol1'] ) self.tmp_containers.append(ctnr) self.client.start(ctnr) self.client.wait(ctnr) strm, stat = self.client.get_archive(ctnr, '/vol1/data.txt') self.assertIn('name', stat) self.assertEqual(stat['name'], 'data.txt') self.assertIn('size', stat) self.assertEqual(stat['size'], len(data)) def test_copy_file_to_container(self): data = b'Deaf To All But The Song' with tempfile.NamedTemporaryFile() as test_file: test_file.write(data) test_file.seek(0) ctnr = self.client.create_container( BUSYBOX, 'cat {0}'.format( os.path.join('/vol1', os.path.basename(test_file.name)) ), volumes=['/vol1'] ) self.tmp_containers.append(ctnr) with helpers.simple_tar(test_file.name) as test_tar: self.client.put_archive(ctnr, '/vol1', test_tar) self.client.start(ctnr) self.client.wait(ctnr) logs = self.client.logs(ctnr) if six.PY3: logs = logs.decode('utf-8') data = data.decode('utf-8') self.assertEqual(logs.strip(), data) def test_copy_directory_to_container(self): files = ['a.py', 'b.py', 'foo/b.py'] dirs = ['foo', 'bar'] base = helpers.make_tree(dirs, files) ctnr = self.client.create_container( BUSYBOX, 'ls -p /vol1', volumes=['/vol1'] ) self.tmp_containers.append(ctnr) with docker.utils.tar(base) as test_tar: self.client.put_archive(ctnr, '/vol1', test_tar) self.client.start(ctnr) self.client.wait(ctnr) logs = self.client.logs(ctnr) if six.PY3: logs = logs.decode('utf-8') results = logs.strip().split() self.assertIn('a.py', results) self.assertIn('b.py', results) self.assertIn('foo/', results) self.assertIn('bar/', results) class RenameContainerTest(helpers.BaseTestCase): def test_rename_container(self): version = self.client.version()['Version'] name = 'hong_meiling' res = self.client.create_container(BUSYBOX, 'true') self.assertIn('Id', res) self.tmp_containers.append(res['Id']) self.client.rename(res, name) inspect = self.client.inspect_container(res['Id']) self.assertIn('Name', inspect) if version == '1.5.0': self.assertEqual(name, inspect['Name']) else: self.assertEqual('/{0}'.format(name), inspect['Name']) class StartContainerTest(helpers.BaseTestCase): def test_start_container(self): res = self.client.create_container(BUSYBOX, 'true') self.assertIn('Id', res) self.tmp_containers.append(res['Id']) self.client.start(res['Id']) inspect = self.client.inspect_container(res['Id']) self.assertIn('Config', inspect) self.assertIn('Id', inspect) self.assertTrue(inspect['Id'].startswith(res['Id'])) self.assertIn('Image', inspect) self.assertIn('State', inspect) self.assertIn('Running', inspect['State']) if not inspect['State']['Running']: self.assertIn('ExitCode', inspect['State']) self.assertEqual(inspect['State']['ExitCode'], 0) def test_start_container_with_dict_instead_of_id(self): res = self.client.create_container(BUSYBOX, 'true') self.assertIn('Id', res) self.tmp_containers.append(res['Id']) self.client.start(res) inspect = self.client.inspect_container(res['Id']) self.assertIn('Config', inspect) self.assertIn('Id', inspect) self.assertTrue(inspect['Id'].startswith(res['Id'])) self.assertIn('Image', inspect) self.assertIn('State', inspect) self.assertIn('Running', inspect['State']) if not inspect['State']['Running']: self.assertIn('ExitCode', inspect['State']) self.assertEqual(inspect['State']['ExitCode'], 0) def test_run_shlex_commands(self): commands = [ 'true', 'echo "The Young Descendant of Tepes & Septette for the ' 'Dead Princess"', 'echo -n "The Young Descendant of Tepes & Septette for the ' 'Dead Princess"', '/bin/sh -c "echo Hello World"', '/bin/sh -c \'echo "Hello World"\'', 'echo "\"Night of Nights\""', 'true && echo "Night of Nights"' ] for cmd in commands: container = self.client.create_container(BUSYBOX, cmd) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) exitcode = self.client.wait(id) self.assertEqual(exitcode, 0, msg=cmd) class WaitTest(helpers.BaseTestCase): def test_wait(self): res = self.client.create_container(BUSYBOX, ['sleep', '3']) id = res['Id'] self.tmp_containers.append(id) self.client.start(id) exitcode = self.client.wait(id) self.assertEqual(exitcode, 0) inspect = self.client.inspect_container(id) self.assertIn('Running', inspect['State']) self.assertEqual(inspect['State']['Running'], False) self.assertIn('ExitCode', inspect['State']) self.assertEqual(inspect['State']['ExitCode'], exitcode) def test_wait_with_dict_instead_of_id(self): res = self.client.create_container(BUSYBOX, ['sleep', '3']) id = res['Id'] self.tmp_containers.append(id) self.client.start(res) exitcode = self.client.wait(res) self.assertEqual(exitcode, 0) inspect = self.client.inspect_container(res) self.assertIn('Running', inspect['State']) self.assertEqual(inspect['State']['Running'], False) self.assertIn('ExitCode', inspect['State']) self.assertEqual(inspect['State']['ExitCode'], exitcode) class LogsTest(helpers.BaseTestCase): def test_logs(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( BUSYBOX, 'echo {0}'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) self.client.start(id) exitcode = self.client.wait(id) self.assertEqual(exitcode, 0) logs = self.client.logs(id) self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) def test_logs_tail_option(self): snippet = '''Line1 Line2''' container = self.client.create_container( BUSYBOX, 'echo "{0}"'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) self.client.start(id) exitcode = self.client.wait(id) self.assertEqual(exitcode, 0) logs = self.client.logs(id, tail=1) self.assertEqual(logs, 'Line2\n'.encode(encoding='ascii')) def test_logs_streaming_and_follow(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( BUSYBOX, 'echo {0}'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) self.client.start(id) logs = six.binary_type() for chunk in self.client.logs(id, stream=True, follow=True): logs += chunk exitcode = self.client.wait(id) self.assertEqual(exitcode, 0) self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) def test_logs_with_dict_instead_of_id(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( BUSYBOX, 'echo {0}'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) self.client.start(id) exitcode = self.client.wait(id) self.assertEqual(exitcode, 0) logs = self.client.logs(container) self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) def test_logs_with_tail_0(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( BUSYBOX, 'echo "{0}"'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) self.client.start(id) exitcode = self.client.wait(id) self.assertEqual(exitcode, 0) logs = self.client.logs(id, tail=0) self.assertEqual(logs, ''.encode(encoding='ascii')) class DiffTest(helpers.BaseTestCase): def test_diff(self): container = self.client.create_container(BUSYBOX, ['touch', '/test']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) exitcode = self.client.wait(id) self.assertEqual(exitcode, 0) diff = self.client.diff(id) test_diff = [x for x in diff if x.get('Path', None) == '/test'] self.assertEqual(len(test_diff), 1) self.assertIn('Kind', test_diff[0]) self.assertEqual(test_diff[0]['Kind'], 1) def test_diff_with_dict_instead_of_id(self): container = self.client.create_container(BUSYBOX, ['touch', '/test']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) exitcode = self.client.wait(id) self.assertEqual(exitcode, 0) diff = self.client.diff(container) test_diff = [x for x in diff if x.get('Path', None) == '/test'] self.assertEqual(len(test_diff), 1) self.assertIn('Kind', test_diff[0]) self.assertEqual(test_diff[0]['Kind'], 1) class StopTest(helpers.BaseTestCase): def test_stop(self): container = self.client.create_container(BUSYBOX, ['sleep', '9999']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) self.client.stop(id, timeout=2) container_info = self.client.inspect_container(id) self.assertIn('State', container_info) state = container_info['State'] self.assertIn('Running', state) self.assertEqual(state['Running'], False) def test_stop_with_dict_instead_of_id(self): container = self.client.create_container(BUSYBOX, ['sleep', '9999']) self.assertIn('Id', container) id = container['Id'] self.client.start(container) self.tmp_containers.append(id) self.client.stop(container, timeout=2) container_info = self.client.inspect_container(id) self.assertIn('State', container_info) state = container_info['State'] self.assertIn('Running', state) self.assertEqual(state['Running'], False) class KillTest(helpers.BaseTestCase): def test_kill(self): container = self.client.create_container(BUSYBOX, ['sleep', '9999']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) self.client.kill(id) container_info = self.client.inspect_container(id) self.assertIn('State', container_info) state = container_info['State'] self.assertIn('ExitCode', state) if helpers.exec_driver_is_native(): self.assertNotEqual(state['ExitCode'], 0) self.assertIn('Running', state) self.assertEqual(state['Running'], False) def test_kill_with_dict_instead_of_id(self): container = self.client.create_container(BUSYBOX, ['sleep', '9999']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) self.client.kill(container) container_info = self.client.inspect_container(id) self.assertIn('State', container_info) state = container_info['State'] self.assertIn('ExitCode', state) if helpers.exec_driver_is_native(): self.assertNotEqual(state['ExitCode'], 0) self.assertIn('Running', state) self.assertEqual(state['Running'], False) def test_kill_with_signal(self): container = self.client.create_container(BUSYBOX, ['sleep', '60']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) self.client.kill(id, signal=signal.SIGKILL) exitcode = self.client.wait(id) self.assertNotEqual(exitcode, 0) container_info = self.client.inspect_container(id) self.assertIn('State', container_info) state = container_info['State'] self.assertIn('ExitCode', state) self.assertNotEqual(state['ExitCode'], 0) self.assertIn('Running', state) self.assertEqual(state['Running'], False, state) class PortTest(helpers.BaseTestCase): def test_port(self): port_bindings = { '1111': ('127.0.0.1', '4567'), '2222': ('127.0.0.1', '4568') } container = self.client.create_container( BUSYBOX, ['sleep', '60'], ports=list(port_bindings.keys()), host_config=self.client.create_host_config( port_bindings=port_bindings, network_mode='bridge' ) ) id = container['Id'] self.client.start(container) # Call the port function on each biding and compare expected vs actual for port in port_bindings: actual_bindings = self.client.port(container, port) port_binding = actual_bindings.pop() ip, host_port = port_binding['HostIp'], port_binding['HostPort'] self.assertEqual(ip, port_bindings[port][0]) self.assertEqual(host_port, port_bindings[port][1]) self.client.kill(id) class ContainerTopTest(helpers.BaseTestCase): def test_top(self): container = self.client.create_container( BUSYBOX, ['sleep', '60']) id = container['Id'] self.client.start(container) res = self.client.top(container['Id']) self.assertEqual( res['Titles'], ['UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD'] ) self.assertEqual(len(res['Processes']), 1) self.assertEqual(res['Processes'][0][7], 'sleep 60') self.client.kill(id) def test_top_with_psargs(self): container = self.client.create_container( BUSYBOX, ['sleep', '60']) id = container['Id'] self.client.start(container) res = self.client.top(container['Id'], 'waux') self.assertEqual( res['Titles'], ['USER', 'PID', '%CPU', '%MEM', 'VSZ', 'RSS', 'TTY', 'STAT', 'START', 'TIME', 'COMMAND'], ) self.assertEqual(len(res['Processes']), 1) self.assertEqual(res['Processes'][0][10], 'sleep 60') self.client.kill(id) class RestartContainerTest(helpers.BaseTestCase): def test_restart(self): container = self.client.create_container(BUSYBOX, ['sleep', '9999']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) info = self.client.inspect_container(id) self.assertIn('State', info) self.assertIn('StartedAt', info['State']) start_time1 = info['State']['StartedAt'] self.client.restart(id, timeout=2) info2 = self.client.inspect_container(id) self.assertIn('State', info2) self.assertIn('StartedAt', info2['State']) start_time2 = info2['State']['StartedAt'] self.assertNotEqual(start_time1, start_time2) self.assertIn('Running', info2['State']) self.assertEqual(info2['State']['Running'], True) self.client.kill(id) def test_restart_with_dict_instead_of_id(self): container = self.client.create_container(BUSYBOX, ['sleep', '9999']) self.assertIn('Id', container) id = container['Id'] self.client.start(container) self.tmp_containers.append(id) info = self.client.inspect_container(id) self.assertIn('State', info) self.assertIn('StartedAt', info['State']) start_time1 = info['State']['StartedAt'] self.client.restart(container, timeout=2) info2 = self.client.inspect_container(id) self.assertIn('State', info2) self.assertIn('StartedAt', info2['State']) start_time2 = info2['State']['StartedAt'] self.assertNotEqual(start_time1, start_time2) self.assertIn('Running', info2['State']) self.assertEqual(info2['State']['Running'], True) self.client.kill(id) class RemoveContainerTest(helpers.BaseTestCase): def test_remove(self): container = self.client.create_container(BUSYBOX, ['true']) id = container['Id'] self.client.start(id) self.client.wait(id) self.client.remove_container(id) containers = self.client.containers(all=True) res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] self.assertEqual(len(res), 0) def test_remove_with_dict_instead_of_id(self): container = self.client.create_container(BUSYBOX, ['true']) id = container['Id'] self.client.start(id) self.client.wait(id) self.client.remove_container(container) containers = self.client.containers(all=True) res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] self.assertEqual(len(res), 0) class AttachContainerTest(helpers.BaseTestCase): def test_run_container_streaming(self): container = self.client.create_container(BUSYBOX, '/bin/sh', detach=True, stdin_open=True) id = container['Id'] self.tmp_containers.append(id) self.client.start(id) sock = self.client.attach_socket(container, ws=False) self.assertTrue(sock.fileno() > -1) def test_run_container_reading_socket(self): line = 'hi there and stuff and things, words!' # `echo` appends CRLF, `printf` doesn't command = "printf '{0}'".format(line) container = self.client.create_container(BUSYBOX, command, detach=True, tty=False) ident = container['Id'] self.tmp_containers.append(ident) opts = {"stdout": 1, "stream": 1, "logs": 1} pty_stdout = self.client.attach_socket(ident, opts) self.addCleanup(pty_stdout.close) self.client.start(ident) next_size = helpers.next_packet_size(pty_stdout) self.assertEqual(next_size, len(line)) data = helpers.read_data(pty_stdout, next_size) self.assertEqual(data.decode('utf-8'), line) class PauseTest(helpers.BaseTestCase): def test_pause_unpause(self): container = self.client.create_container(BUSYBOX, ['sleep', '9999']) id = container['Id'] self.tmp_containers.append(id) self.client.start(container) self.client.pause(id) container_info = self.client.inspect_container(id) self.assertIn('State', container_info) state = container_info['State'] self.assertIn('ExitCode', state) self.assertEqual(state['ExitCode'], 0) self.assertIn('Running', state) self.assertEqual(state['Running'], True) self.assertIn('Paused', state) self.assertEqual(state['Paused'], True) self.client.unpause(id) container_info = self.client.inspect_container(id) self.assertIn('State', container_info) state = container_info['State'] self.assertIn('ExitCode', state) self.assertEqual(state['ExitCode'], 0) self.assertIn('Running', state) self.assertEqual(state['Running'], True) self.assertIn('Paused', state) self.assertEqual(state['Paused'], False) class GetContainerStatsTest(helpers.BaseTestCase): @requires_api_version('1.19') def test_get_container_stats_no_stream(self): container = self.client.create_container( BUSYBOX, ['sleep', '60'], ) self.tmp_containers.append(container) self.client.start(container) response = self.client.stats(container, stream=0) self.client.kill(container) self.assertEqual(type(response), dict) for key in ['read', 'networks', 'precpu_stats', 'cpu_stats', 'memory_stats', 'blkio_stats']: self.assertIn(key, response) @requires_api_version('1.17') def test_get_container_stats_stream(self): container = self.client.create_container( BUSYBOX, ['sleep', '60'], ) self.tmp_containers.append(container) self.client.start(container) stream = self.client.stats(container) for chunk in stream: self.assertEqual(type(chunk), dict) for key in ['read', 'network', 'precpu_stats', 'cpu_stats', 'memory_stats', 'blkio_stats']: self.assertIn(key, chunk) class ContainerUpdateTest(helpers.BaseTestCase): @requires_api_version('1.22') def test_update_container(self): old_mem_limit = 400 * 1024 * 1024 new_mem_limit = 300 * 1024 * 1024 container = self.client.create_container( BUSYBOX, 'top', host_config=self.client.create_host_config( mem_limit=old_mem_limit ), cpu_shares=102 ) self.tmp_containers.append(container) self.client.start(container) self.client.update_container(container, mem_limit=new_mem_limit) inspect_data = self.client.inspect_container(container) self.assertEqual(inspect_data['HostConfig']['Memory'], new_mem_limit) self.assertEqual(inspect_data['HostConfig']['CpuShares'], 102) docker-py-1.8.0/tests/integration/exec_test.py0000644000175000017500000001110112702731056020060 0ustar kickkickimport pytest from .. import helpers BUSYBOX = helpers.BUSYBOX class ExecTest(helpers.BaseTestCase): def test_execute_command(self): if not helpers.exec_driver_is_native(): pytest.skip('Exec driver not native') container = self.client.create_container(BUSYBOX, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) res = self.client.exec_create(id, ['echo', 'hello']) self.assertIn('Id', res) exec_log = self.client.exec_start(res) self.assertEqual(exec_log, b'hello\n') def test_exec_command_string(self): if not helpers.exec_driver_is_native(): pytest.skip('Exec driver not native') container = self.client.create_container(BUSYBOX, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) res = self.client.exec_create(id, 'echo hello world') self.assertIn('Id', res) exec_log = self.client.exec_start(res) self.assertEqual(exec_log, b'hello world\n') def test_exec_command_as_user(self): if not helpers.exec_driver_is_native(): pytest.skip('Exec driver not native') container = self.client.create_container(BUSYBOX, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) res = self.client.exec_create(id, 'whoami', user='default') self.assertIn('Id', res) exec_log = self.client.exec_start(res) self.assertEqual(exec_log, b'default\n') def test_exec_command_as_root(self): if not helpers.exec_driver_is_native(): pytest.skip('Exec driver not native') container = self.client.create_container(BUSYBOX, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) res = self.client.exec_create(id, 'whoami') self.assertIn('Id', res) exec_log = self.client.exec_start(res) self.assertEqual(exec_log, b'root\n') def test_exec_command_streaming(self): if not helpers.exec_driver_is_native(): pytest.skip('Exec driver not native') container = self.client.create_container(BUSYBOX, 'cat', detach=True, stdin_open=True) id = container['Id'] self.tmp_containers.append(id) self.client.start(id) exec_id = self.client.exec_create(id, ['echo', 'hello\nworld']) self.assertIn('Id', exec_id) res = b'' for chunk in self.client.exec_start(exec_id, stream=True): res += chunk self.assertEqual(res, b'hello\nworld\n') def test_exec_start_socket(self): if not helpers.exec_driver_is_native(): pytest.skip('Exec driver not native') container = self.client.create_container(BUSYBOX, 'cat', detach=True, stdin_open=True) container_id = container['Id'] self.client.start(container_id) self.tmp_containers.append(container_id) line = 'yay, interactive exec!' # `echo` appends CRLF, `printf` doesn't exec_id = self.client.exec_create( container_id, ['printf', line], tty=True) self.assertIn('Id', exec_id) socket = self.client.exec_start(exec_id, socket=True) self.addCleanup(socket.close) next_size = helpers.next_packet_size(socket) self.assertEqual(next_size, len(line)) data = helpers.read_data(socket, next_size) self.assertEqual(data.decode('utf-8'), line) def test_exec_inspect(self): if not helpers.exec_driver_is_native(): pytest.skip('Exec driver not native') container = self.client.create_container(BUSYBOX, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) exec_id = self.client.exec_create(id, ['mkdir', '/does/not/exist']) self.assertIn('Id', exec_id) self.client.exec_start(exec_id) exec_info = self.client.exec_inspect(exec_id) self.assertIn('ExitCode', exec_info) self.assertNotEqual(exec_info['ExitCode'], 0) docker-py-1.8.0/tests/integration/image_test.py0000644000175000017500000002074012702731056020227 0ustar kickkickimport contextlib import json import shutil import socket import tarfile import tempfile import threading import pytest import six from six.moves import BaseHTTPServer from six.moves import socketserver import docker from .. import helpers BUSYBOX = helpers.BUSYBOX class ListImagesTest(helpers.BaseTestCase): def test_images(self): res1 = self.client.images(all=True) self.assertIn('Id', res1[0]) res10 = res1[0] self.assertIn('Created', res10) self.assertIn('RepoTags', res10) distinct = [] for img in res1: if img['Id'] not in distinct: distinct.append(img['Id']) self.assertEqual(len(distinct), self.client.info()['Images']) def test_images_quiet(self): res1 = self.client.images(quiet=True) self.assertEqual(type(res1[0]), six.text_type) class PullImageTest(helpers.BaseTestCase): def test_pull(self): try: self.client.remove_image('hello-world') except docker.errors.APIError: pass res = self.client.pull('hello-world') self.tmp_imgs.append('hello-world') self.assertEqual(type(res), six.text_type) self.assertGreaterEqual( len(self.client.images('hello-world')), 1 ) img_info = self.client.inspect_image('hello-world') self.assertIn('Id', img_info) def test_pull_streaming(self): try: self.client.remove_image('hello-world') except docker.errors.APIError: pass stream = self.client.pull('hello-world', stream=True) self.tmp_imgs.append('hello-world') for chunk in stream: if six.PY3: chunk = chunk.decode('utf-8') json.loads(chunk) # ensure chunk is a single, valid JSON blob self.assertGreaterEqual( len(self.client.images('hello-world')), 1 ) img_info = self.client.inspect_image('hello-world') self.assertIn('Id', img_info) class CommitTest(helpers.BaseTestCase): def test_commit(self): container = self.client.create_container(BUSYBOX, ['touch', '/test']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) res = self.client.commit(id) self.assertIn('Id', res) img_id = res['Id'] self.tmp_imgs.append(img_id) img = self.client.inspect_image(img_id) self.assertIn('Container', img) self.assertTrue(img['Container'].startswith(id)) self.assertIn('ContainerConfig', img) self.assertIn('Image', img['ContainerConfig']) self.assertEqual(BUSYBOX, img['ContainerConfig']['Image']) busybox_id = self.client.inspect_image(BUSYBOX)['Id'] self.assertIn('Parent', img) self.assertEqual(img['Parent'], busybox_id) def test_commit_with_changes(self): cid = self.client.create_container(BUSYBOX, ['touch', '/test']) self.tmp_containers.append(cid) self.client.start(cid) img_id = self.client.commit( cid, changes=['EXPOSE 8000', 'CMD ["bash"]'] ) self.tmp_imgs.append(img_id) img = self.client.inspect_image(img_id) assert 'Container' in img assert img['Container'].startswith(cid['Id']) assert '8000/tcp' in img['Config']['ExposedPorts'] assert img['Config']['Cmd'] == ['bash'] class RemoveImageTest(helpers.BaseTestCase): def test_remove(self): container = self.client.create_container(BUSYBOX, ['touch', '/test']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) res = self.client.commit(id) self.assertIn('Id', res) img_id = res['Id'] self.tmp_imgs.append(img_id) self.client.remove_image(img_id, force=True) images = self.client.images(all=True) res = [x for x in images if x['Id'].startswith(img_id)] self.assertEqual(len(res), 0) class ImportImageTest(helpers.BaseTestCase): '''Base class for `docker import` test cases.''' TAR_SIZE = 512 * 1024 def write_dummy_tar_content(self, n_bytes, tar_fd): def extend_file(f, n_bytes): f.seek(n_bytes - 1) f.write(bytearray([65])) f.seek(0) tar = tarfile.TarFile(fileobj=tar_fd, mode='w') with tempfile.NamedTemporaryFile() as f: extend_file(f, n_bytes) tarinfo = tar.gettarinfo(name=f.name, arcname='testdata') tar.addfile(tarinfo, fileobj=f) tar.close() @contextlib.contextmanager def dummy_tar_stream(self, n_bytes): '''Yields a stream that is valid tar data of size n_bytes.''' with tempfile.NamedTemporaryFile() as tar_file: self.write_dummy_tar_content(n_bytes, tar_file) tar_file.seek(0) yield tar_file @contextlib.contextmanager def dummy_tar_file(self, n_bytes): '''Yields the name of a valid tar file of size n_bytes.''' with tempfile.NamedTemporaryFile() as tar_file: self.write_dummy_tar_content(n_bytes, tar_file) tar_file.seek(0) yield tar_file.name def test_import_from_bytes(self): with self.dummy_tar_stream(n_bytes=500) as f: content = f.read() # The generic import_image() function cannot import in-memory bytes # data that happens to be represented as a string type, because # import_image() will try to use it as a filename and usually then # trigger an exception. So we test the import_image_from_data() # function instead. statuses = self.client.import_image_from_data( content, repository='test/import-from-bytes') result_text = statuses.splitlines()[-1] result = json.loads(result_text) self.assertNotIn('error', result) img_id = result['status'] self.tmp_imgs.append(img_id) def test_import_from_file(self): with self.dummy_tar_file(n_bytes=self.TAR_SIZE) as tar_filename: # statuses = self.client.import_image( # src=tar_filename, repository='test/import-from-file') statuses = self.client.import_image_from_file( tar_filename, repository='test/import-from-file') result_text = statuses.splitlines()[-1] result = json.loads(result_text) self.assertNotIn('error', result) self.assertIn('status', result) img_id = result['status'] self.tmp_imgs.append(img_id) def test_import_from_stream(self): with self.dummy_tar_stream(n_bytes=self.TAR_SIZE) as tar_stream: statuses = self.client.import_image( src=tar_stream, repository='test/import-from-stream') # statuses = self.client.import_image_from_stream( # tar_stream, repository='test/import-from-stream') result_text = statuses.splitlines()[-1] result = json.loads(result_text) self.assertNotIn('error', result) self.assertIn('status', result) img_id = result['status'] self.tmp_imgs.append(img_id) @contextlib.contextmanager def temporary_http_file_server(self, stream): '''Serve data from an IO stream over HTTP.''' class Handler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-Type', 'application/x-tar') self.end_headers() shutil.copyfileobj(stream, self.wfile) server = socketserver.TCPServer(('', 0), Handler) thread = threading.Thread(target=server.serve_forever) thread.setDaemon(True) thread.start() yield 'http://%s:%s' % (socket.gethostname(), server.server_address[1]) server.shutdown() @pytest.mark.skipif(True, reason="Doesn't work inside a container - FIXME") def test_import_from_url(self): # The crappy test HTTP server doesn't handle large files well, so use # a small file. tar_size = 10240 with self.dummy_tar_stream(n_bytes=tar_size) as tar_data: with self.temporary_http_file_server(tar_data) as url: statuses = self.client.import_image( src=url, repository='test/import-from-url') result_text = statuses.splitlines()[-1] result = json.loads(result_text) self.assertNotIn('error', result) self.assertIn('status', result) img_id = result['status'] self.tmp_imgs.append(img_id) docker-py-1.8.0/tests/integration/network_test.py0000644000175000017500000002523212702731056020637 0ustar kickkickimport random import docker from docker.utils import create_ipam_config from docker.utils import create_ipam_pool import pytest from .. import helpers from ..base import requires_api_version class TestNetworks(helpers.BaseTestCase): def create_network(self, *args, **kwargs): net_name = u'dockerpy{}'.format(random.getrandbits(24))[:14] net_id = self.client.create_network(net_name, *args, **kwargs)['Id'] self.tmp_networks.append(net_id) return (net_name, net_id) @requires_api_version('1.21') def test_list_networks(self): networks = self.client.networks() initial_size = len(networks) net_name, net_id = self.create_network() networks = self.client.networks() self.assertEqual(len(networks), initial_size + 1) self.assertTrue(net_id in [n['Id'] for n in networks]) networks_by_name = self.client.networks(names=[net_name]) self.assertEqual([n['Id'] for n in networks_by_name], [net_id]) networks_by_partial_id = self.client.networks(ids=[net_id[:8]]) self.assertEqual([n['Id'] for n in networks_by_partial_id], [net_id]) @requires_api_version('1.21') def test_inspect_network(self): net_name, net_id = self.create_network() net = self.client.inspect_network(net_id) self.assertEqual(net['Id'], net_id) self.assertEqual(net['Name'], net_name) self.assertEqual(net['Driver'], 'bridge') self.assertEqual(net['Scope'], 'local') self.assertEqual(net['IPAM']['Driver'], 'default') @requires_api_version('1.21') def test_create_network_with_ipam_config(self): _, net_id = self.create_network( ipam=create_ipam_config( driver='default', pool_configs=[ create_ipam_pool( subnet="172.28.0.0/16", iprange="172.28.5.0/24", gateway="172.28.5.254", aux_addresses={ "a": "172.28.1.5", "b": "172.28.1.6", "c": "172.28.1.7", }, ), ], ), ) net = self.client.inspect_network(net_id) ipam = net['IPAM'] assert ipam.pop('Options', None) is None assert ipam == { 'Driver': 'default', 'Config': [{ 'Subnet': "172.28.0.0/16", 'IPRange': "172.28.5.0/24", 'Gateway': "172.28.5.254", 'AuxiliaryAddresses': { "a": "172.28.1.5", "b": "172.28.1.6", "c": "172.28.1.7", }, }], } @requires_api_version('1.21') def test_create_network_with_host_driver_fails(self): net_name = 'dockerpy{}'.format(random.getrandbits(24))[:14] with pytest.raises(docker.errors.APIError): self.client.create_network(net_name, driver='host') @requires_api_version('1.21') def test_remove_network(self): initial_size = len(self.client.networks()) net_name, net_id = self.create_network() self.assertEqual(len(self.client.networks()), initial_size + 1) self.client.remove_network(net_id) self.assertEqual(len(self.client.networks()), initial_size) @requires_api_version('1.21') def test_connect_and_disconnect_container(self): net_name, net_id = self.create_network() container = self.client.create_container('busybox', 'top') self.tmp_containers.append(container) self.client.start(container) network_data = self.client.inspect_network(net_id) self.assertFalse(network_data.get('Containers')) self.client.connect_container_to_network(container, net_id) network_data = self.client.inspect_network(net_id) self.assertEqual( list(network_data['Containers'].keys()), [container['Id']]) with pytest.raises(docker.errors.APIError): self.client.connect_container_to_network(container, net_id) self.client.disconnect_container_from_network(container, net_id) network_data = self.client.inspect_network(net_id) self.assertFalse(network_data.get('Containers')) with pytest.raises(docker.errors.APIError): self.client.disconnect_container_from_network(container, net_id) @requires_api_version('1.22') def test_connect_with_aliases(self): net_name, net_id = self.create_network() container = self.client.create_container('busybox', 'top') self.tmp_containers.append(container) self.client.start(container) self.client.connect_container_to_network( container, net_id, aliases=['foo', 'bar']) container_data = self.client.inspect_container(container) self.assertEqual( container_data['NetworkSettings']['Networks'][net_name]['Aliases'], ['foo', 'bar']) @requires_api_version('1.21') def test_connect_on_container_create(self): net_name, net_id = self.create_network() container = self.client.create_container( image='busybox', command='top', host_config=self.client.create_host_config(network_mode=net_name), ) self.tmp_containers.append(container) self.client.start(container) network_data = self.client.inspect_network(net_id) self.assertEqual( list(network_data['Containers'].keys()), [container['Id']]) self.client.disconnect_container_from_network(container, net_id) network_data = self.client.inspect_network(net_id) self.assertFalse(network_data.get('Containers')) @requires_api_version('1.22') def test_create_with_aliases(self): net_name, net_id = self.create_network() container = self.client.create_container( image='busybox', command='top', host_config=self.client.create_host_config( network_mode=net_name, ), networking_config=self.client.create_networking_config({ net_name: self.client.create_endpoint_config( aliases=['foo', 'bar'], ), }), ) self.tmp_containers.append(container) self.client.start(container) container_data = self.client.inspect_container(container) self.assertEqual( container_data['NetworkSettings']['Networks'][net_name]['Aliases'], ['foo', 'bar']) @requires_api_version('1.22') def test_create_with_links(self): net_name, net_id = self.create_network() container = self.create_and_start( host_config=self.client.create_host_config(network_mode=net_name), networking_config=self.client.create_networking_config({ net_name: self.client.create_endpoint_config( links=[('docker-py-test-upstream', 'bar')], ), }), ) container_data = self.client.inspect_container(container) self.assertEqual( container_data['NetworkSettings']['Networks'][net_name]['Links'], ['docker-py-test-upstream:bar']) self.create_and_start( name='docker-py-test-upstream', host_config=self.client.create_host_config(network_mode=net_name), ) self.execute(container, ['nslookup', 'bar']) @requires_api_version('1.21') def test_create_check_duplicate(self): net_name, net_id = self.create_network() with self.assertRaises(docker.errors.APIError): self.client.create_network(net_name, check_duplicate=True) self.client.create_network(net_name, check_duplicate=False) @requires_api_version('1.22') def test_connect_with_links(self): net_name, net_id = self.create_network() container = self.create_and_start( host_config=self.client.create_host_config(network_mode=net_name)) self.client.disconnect_container_from_network(container, net_name) self.client.connect_container_to_network( container, net_name, links=[('docker-py-test-upstream', 'bar')]) container_data = self.client.inspect_container(container) self.assertEqual( container_data['NetworkSettings']['Networks'][net_name]['Links'], ['docker-py-test-upstream:bar']) self.create_and_start( name='docker-py-test-upstream', host_config=self.client.create_host_config(network_mode=net_name), ) self.execute(container, ['nslookup', 'bar']) @requires_api_version('1.22') def test_connect_with_ipv4_address(self): net_name, net_id = self.create_network( ipam=create_ipam_config( driver='default', pool_configs=[ create_ipam_pool( subnet="172.28.0.0/16", iprange="172.28.5.0/24", gateway="172.28.5.254" ) ] ) ) container = self.create_and_start( host_config=self.client.create_host_config(network_mode=net_name)) self.client.disconnect_container_from_network(container, net_name) self.client.connect_container_to_network( container, net_name, ipv4_address='172.28.5.24' ) container_data = self.client.inspect_container(container) net_data = container_data['NetworkSettings']['Networks'][net_name] self.assertEqual( net_data['IPAMConfig']['IPv4Address'], '172.28.5.24' ) @requires_api_version('1.22') def test_connect_with_ipv6_address(self): net_name, net_id = self.create_network( ipam=create_ipam_config( driver='default', pool_configs=[ create_ipam_pool( subnet="2001:389::1/64", iprange="2001:389::0/96", gateway="2001:389::ffff" ) ] ) ) container = self.create_and_start( host_config=self.client.create_host_config(network_mode=net_name)) self.client.disconnect_container_from_network(container, net_name) self.client.connect_container_to_network( container, net_name, ipv6_address='2001:389::f00d' ) container_data = self.client.inspect_container(container) net_data = container_data['NetworkSettings']['Networks'][net_name] self.assertEqual( net_data['IPAMConfig']['IPv6Address'], '2001:389::f00d' ) docker-py-1.8.0/tests/integration/regression_test.py0000644000175000017500000000442112702731056021323 0ustar kickkickimport io import random import docker import six from .. import helpers BUSYBOX = helpers.BUSYBOX class TestRegressions(helpers.BaseTestCase): def test_443_handle_nonchunked_response_in_stream(self): dfile = io.BytesIO() with self.assertRaises(docker.errors.APIError) as exc: for line in self.client.build(fileobj=dfile, tag="a/b/c"): pass self.assertEqual(exc.exception.response.status_code, 500) dfile.close() def test_542_truncate_ids_client_side(self): self.client.start( self.client.create_container(BUSYBOX, ['true']) ) result = self.client.containers(all=True, trunc=True) self.assertEqual(len(result[0]['Id']), 12) def test_647_support_doubleslash_in_image_names(self): with self.assertRaises(docker.errors.APIError): self.client.inspect_image('gensokyo.jp//kirisame') def test_649_handle_timeout_value_none(self): self.client.timeout = None ctnr = self.client.create_container(BUSYBOX, ['sleep', '2']) self.client.start(ctnr) self.client.stop(ctnr) def test_715_handle_user_param_as_int_value(self): ctnr = self.client.create_container(BUSYBOX, ['id', '-u'], user=1000) self.client.start(ctnr) self.client.wait(ctnr) logs = self.client.logs(ctnr) if six.PY3: logs = logs.decode('utf-8') assert logs == '1000\n' def test_792_explicit_port_protocol(self): tcp_port, udp_port = random.sample(range(9999, 32000), 2) ctnr = self.client.create_container( BUSYBOX, ['sleep', '9999'], ports=[2000, (2000, 'udp')], host_config=self.client.create_host_config( port_bindings={'2000/tcp': tcp_port, '2000/udp': udp_port} ) ) self.tmp_containers.append(ctnr) self.client.start(ctnr) self.assertEqual( self.client.port(ctnr, 2000)[0]['HostPort'], six.text_type(tcp_port) ) self.assertEqual( self.client.port(ctnr, '2000/tcp')[0]['HostPort'], six.text_type(tcp_port) ) self.assertEqual( self.client.port(ctnr, '2000/udp')[0]['HostPort'], six.text_type(udp_port) ) docker-py-1.8.0/tests/integration/volume_test.py0000644000175000017500000000347412702731056020461 0ustar kickkickimport docker import pytest from .. import helpers from ..base import requires_api_version @requires_api_version('1.21') class TestVolumes(helpers.BaseTestCase): def test_create_volume(self): name = 'perfectcherryblossom' self.tmp_volumes.append(name) result = self.client.create_volume(name) self.assertIn('Name', result) self.assertEqual(result['Name'], name) self.assertIn('Driver', result) self.assertEqual(result['Driver'], 'local') def test_create_volume_invalid_driver(self): driver_name = 'invalid.driver' with pytest.raises(docker.errors.NotFound): self.client.create_volume('perfectcherryblossom', driver_name) def test_list_volumes(self): name = 'imperishablenight' self.tmp_volumes.append(name) volume_info = self.client.create_volume(name) result = self.client.volumes() self.assertIn('Volumes', result) volumes = result['Volumes'] self.assertIn(volume_info, volumes) def test_inspect_volume(self): name = 'embodimentofscarletdevil' self.tmp_volumes.append(name) volume_info = self.client.create_volume(name) result = self.client.inspect_volume(name) self.assertEqual(volume_info, result) def test_inspect_nonexistent_volume(self): name = 'embodimentofscarletdevil' with pytest.raises(docker.errors.NotFound): self.client.inspect_volume(name) def test_remove_volume(self): name = 'shootthebullet' self.tmp_volumes.append(name) self.client.create_volume(name) self.client.remove_volume(name) def test_remove_nonexistent_volume(self): name = 'shootthebullet' with pytest.raises(docker.errors.NotFound): self.client.remove_volume(name) docker-py-1.8.0/tests/unit/0000755000175000017500000000000012702731056014165 5ustar kickkickdocker-py-1.8.0/tests/unit/testdata/0000755000175000017500000000000012702731056015776 5ustar kickkickdocker-py-1.8.0/tests/unit/testdata/certs/0000755000175000017500000000000012702731056017116 5ustar kickkickdocker-py-1.8.0/tests/unit/testdata/certs/ca.pem0000644000175000017500000000000012702731056020172 0ustar kickkickdocker-py-1.8.0/tests/unit/testdata/certs/cert.pem0000644000175000017500000000000012702731056020544 0ustar kickkickdocker-py-1.8.0/tests/unit/testdata/certs/key.pem0000644000175000017500000000000012702731056020377 0ustar kickkickdocker-py-1.8.0/tests/unit/__init__.py0000644000175000017500000000000012702731056016264 0ustar kickkickdocker-py-1.8.0/tests/unit/api_test.py0000644000175000017500000003103412702731056016350 0ustar kickkick# Copyright 2013 dotCloud inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import json import os import re import shutil import socket import sys import tempfile import threading import time import docker import requests import six from .. import base from . import fake_api import pytest try: from unittest import mock except ImportError: import mock DEFAULT_TIMEOUT_SECONDS = docker.constants.DEFAULT_TIMEOUT_SECONDS def response(status_code=200, content='', headers=None, reason=None, elapsed=0, request=None): res = requests.Response() res.status_code = status_code if not isinstance(content, six.binary_type): content = json.dumps(content).encode('ascii') res._content = content res.headers = requests.structures.CaseInsensitiveDict(headers or {}) res.reason = reason res.elapsed = datetime.timedelta(elapsed) res.request = request return res def fake_resolve_authconfig(authconfig, registry=None): return None def fake_inspect_container(self, container, tty=False): return fake_api.get_fake_inspect_container(tty=tty)[1] def fake_resp(method, url, *args, **kwargs): key = None if url in fake_api.fake_responses: key = url elif (url, method) in fake_api.fake_responses: key = (url, method) if not key: raise Exception('{0} {1}'.format(method, url)) status_code, content = fake_api.fake_responses[key]() return response(status_code=status_code, content=content) fake_request = mock.Mock(side_effect=fake_resp) def fake_get(self, url, *args, **kwargs): return fake_request('GET', url, *args, **kwargs) def fake_post(self, url, *args, **kwargs): return fake_request('POST', url, *args, **kwargs) def fake_put(self, url, *args, **kwargs): return fake_request('PUT', url, *args, **kwargs) def fake_delete(self, url, *args, **kwargs): return fake_request('DELETE', url, *args, **kwargs) url_base = 'http+docker://localunixsocket/' url_prefix = '{0}v{1}/'.format( url_base, docker.constants.DEFAULT_DOCKER_API_VERSION) class DockerClientTest(base.Cleanup, base.BaseTestCase): def setUp(self): self.patcher = mock.patch.multiple( 'docker.Client', get=fake_get, post=fake_post, put=fake_put, delete=fake_delete ) self.patcher.start() self.client = docker.Client() # Force-clear authconfig to avoid tampering with the tests self.client._cfg = {'Configs': {}} def tearDown(self): self.client.close() self.patcher.stop() def assertIn(self, object, collection): if six.PY2 and sys.version_info[1] <= 6: return self.assertTrue(object in collection) return super(DockerClientTest, self).assertIn(object, collection) def base_create_payload(self, img='busybox', cmd=None): if not cmd: cmd = ['true'] return {"Tty": False, "Image": img, "Cmd": cmd, "AttachStdin": False, "AttachStderr": True, "AttachStdout": True, "StdinOnce": False, "OpenStdin": False, "NetworkDisabled": False, } class DockerApiTest(DockerClientTest): def test_ctor(self): with pytest.raises(docker.errors.DockerException) as excinfo: docker.Client(version=1.12) self.assertEqual( str(excinfo.value), 'Version parameter must be a string or None. Found float' ) def test_url_valid_resource(self): url = self.client._url('/hello/{0}/world', 'somename') self.assertEqual( url, '{0}{1}'.format(url_prefix, 'hello/somename/world') ) url = self.client._url( '/hello/{0}/world/{1}', 'somename', 'someothername' ) self.assertEqual( url, '{0}{1}'.format(url_prefix, 'hello/somename/world/someothername') ) url = self.client._url('/hello/{0}/world', '/some?name') self.assertEqual( url, '{0}{1}'.format(url_prefix, 'hello/%2Fsome%3Fname/world') ) def test_url_invalid_resource(self): with pytest.raises(ValueError): self.client._url('/hello/{0}/world', ['sakuya', 'izayoi']) def test_url_no_resource(self): url = self.client._url('/simple') self.assertEqual(url, '{0}{1}'.format(url_prefix, 'simple')) def test_url_unversioned_api(self): url = self.client._url( '/hello/{0}/world', 'somename', versioned_api=False ) self.assertEqual( url, '{0}{1}'.format(url_base, 'hello/somename/world') ) def test_version(self): self.client.version() fake_request.assert_called_with( 'GET', url_prefix + 'version', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_version_no_api_version(self): self.client.version(False) fake_request.assert_called_with( 'GET', url_base + 'version', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_retrieve_server_version(self): client = docker.Client(version="auto") self.assertTrue(isinstance(client._version, six.string_types)) self.assertFalse(client._version == "auto") client.close() def test_auto_retrieve_server_version(self): version = self.client._retrieve_server_version() self.assertTrue(isinstance(version, six.string_types)) def test_info(self): self.client.info() fake_request.assert_called_with( 'GET', url_prefix + 'info', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_search(self): self.client.search('busybox') fake_request.assert_called_with( 'GET', url_prefix + 'images/search', params={'term': 'busybox'}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_events(self): self.client.events() fake_request.assert_called_with( 'GET', url_prefix + 'events', params={'since': None, 'until': None, 'filters': None}, stream=True ) def test_events_with_since_until(self): ts = 1356048000 now = datetime.datetime.utcfromtimestamp(ts) since = now - datetime.timedelta(seconds=10) until = now + datetime.timedelta(seconds=10) self.client.events(since=since, until=until) fake_request.assert_called_with( 'GET', url_prefix + 'events', params={ 'since': ts - 10, 'until': ts + 10, 'filters': None }, stream=True ) def test_events_with_filters(self): filters = {'event': ['die', 'stop'], 'container': fake_api.FAKE_CONTAINER_ID} self.client.events(filters=filters) expected_filters = docker.utils.convert_filters(filters) fake_request.assert_called_with( 'GET', url_prefix + 'events', params={ 'since': None, 'until': None, 'filters': expected_filters }, stream=True ) def _socket_path_for_client_session(self, client): socket_adapter = client.get_adapter('http+docker://') return socket_adapter.socket_path def test_url_compatibility_unix(self): c = docker.Client(base_url="unix://socket") assert self._socket_path_for_client_session(c) == '/socket' def test_url_compatibility_unix_triple_slash(self): c = docker.Client(base_url="unix:///socket") assert self._socket_path_for_client_session(c) == '/socket' def test_url_compatibility_http_unix_triple_slash(self): c = docker.Client(base_url="http+unix:///socket") assert self._socket_path_for_client_session(c) == '/socket' def test_url_compatibility_http(self): c = docker.Client(base_url="http://hostname:1234") assert c.base_url == "http://hostname:1234" def test_url_compatibility_tcp(self): c = docker.Client(base_url="tcp://hostname:1234") assert c.base_url == "http://hostname:1234" def test_remove_link(self): self.client.remove_container(fake_api.FAKE_CONTAINER_ID, link=True) fake_request.assert_called_with( 'DELETE', url_prefix + 'containers/3cc2351ab11b', params={'v': False, 'link': True, 'force': False}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_create_host_config_secopt(self): security_opt = ['apparmor:test_profile'] result = self.client.create_host_config(security_opt=security_opt) self.assertIn('SecurityOpt', result) self.assertEqual(result['SecurityOpt'], security_opt) self.assertRaises( TypeError, self.client.create_host_config, security_opt='wrong' ) class StreamTest(base.Cleanup, base.BaseTestCase): def setUp(self): socket_dir = tempfile.mkdtemp() self.build_context = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, socket_dir) self.addCleanup(shutil.rmtree, self.build_context) self.socket_file = os.path.join(socket_dir, 'test_sock.sock') self.server_socket = self._setup_socket() self.stop_server = False server_thread = threading.Thread(target=self.run_server) server_thread.setDaemon(True) server_thread.start() self.response = None self.request_handler = None self.addCleanup(server_thread.join) self.addCleanup(self.stop) def stop(self): self.stop_server = True def _setup_socket(self): server_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) server_sock.bind(self.socket_file) # Non-blocking mode so that we can shut the test down easily server_sock.setblocking(0) server_sock.listen(5) return server_sock def run_server(self): try: while not self.stop_server: try: connection, client_address = self.server_socket.accept() except socket.error: # Probably no connection to accept yet time.sleep(0.01) continue connection.setblocking(1) try: self.request_handler(connection) finally: connection.close() finally: self.server_socket.close() def early_response_sending_handler(self, connection): data = b'' headers = None connection.sendall(self.response) while not headers: data += connection.recv(2048) parts = data.split(b'\r\n\r\n', 1) if len(parts) == 2: headers, data = parts mo = re.search(r'Content-Length: ([0-9]+)', headers.decode()) assert mo content_length = int(mo.group(1)) while True: if len(data) >= content_length: break data += connection.recv(2048) def test_early_stream_response(self): self.request_handler = self.early_response_sending_handler lines = [] for i in range(0, 50): line = str(i).encode() lines += [('%x' % len(line)).encode(), line] lines.append(b'0') lines.append(b'') self.response = ( b'HTTP/1.1 200 OK\r\n' b'Transfer-Encoding: chunked\r\n' b'\r\n' ) + b'\r\n'.join(lines) with docker.Client(base_url="http+unix://" + self.socket_file) \ as client: for i in range(5): try: stream = client.build( path=self.build_context, stream=True ) break except requests.ConnectionError as e: if i == 4: raise e self.assertEqual(list(stream), [ str(i).encode() for i in range(50)]) docker-py-1.8.0/tests/unit/auth_test.py0000644000175000017500000003613512702731056016547 0ustar kickkick# -*- coding: utf-8 -*- import base64 import json import os import os.path import random import shutil import tempfile from docker import auth from docker.auth.auth import parse_auth from docker import errors from .. import base try: from unittest import mock except ImportError: import mock class RegressionTest(base.BaseTestCase): def test_803_urlsafe_encode(self): auth_data = { 'username': 'root', 'password': 'GR?XGR?XGR?XGR?X' } encoded = auth.encode_header(auth_data) assert b'/' not in encoded assert b'_' in encoded class ResolveRepositoryNameTest(base.BaseTestCase): def test_resolve_repository_name_hub_library_image(self): self.assertEqual( auth.resolve_repository_name('image'), ('docker.io', 'image'), ) def test_resolve_repository_name_dotted_hub_library_image(self): self.assertEqual( auth.resolve_repository_name('image.valid'), ('docker.io', 'image.valid') ) def test_resolve_repository_name_hub_image(self): self.assertEqual( auth.resolve_repository_name('username/image'), ('docker.io', 'username/image'), ) def test_explicit_hub_index_library_image(self): self.assertEqual( auth.resolve_repository_name('docker.io/image'), ('docker.io', 'image') ) def test_explicit_legacy_hub_index_library_image(self): self.assertEqual( auth.resolve_repository_name('index.docker.io/image'), ('docker.io', 'image') ) def test_resolve_repository_name_private_registry(self): self.assertEqual( auth.resolve_repository_name('my.registry.net/image'), ('my.registry.net', 'image'), ) def test_resolve_repository_name_private_registry_with_port(self): self.assertEqual( auth.resolve_repository_name('my.registry.net:5000/image'), ('my.registry.net:5000', 'image'), ) def test_resolve_repository_name_private_registry_with_username(self): self.assertEqual( auth.resolve_repository_name('my.registry.net/username/image'), ('my.registry.net', 'username/image'), ) def test_resolve_repository_name_no_dots_but_port(self): self.assertEqual( auth.resolve_repository_name('hostname:5000/image'), ('hostname:5000', 'image'), ) def test_resolve_repository_name_no_dots_but_port_and_username(self): self.assertEqual( auth.resolve_repository_name('hostname:5000/username/image'), ('hostname:5000', 'username/image'), ) def test_resolve_repository_name_localhost(self): self.assertEqual( auth.resolve_repository_name('localhost/image'), ('localhost', 'image'), ) def test_resolve_repository_name_localhost_with_username(self): self.assertEqual( auth.resolve_repository_name('localhost/username/image'), ('localhost', 'username/image'), ) def test_invalid_index_name(self): self.assertRaises( errors.InvalidRepository, lambda: auth.resolve_repository_name('-gecko.com/image') ) def encode_auth(auth_info): return base64.b64encode( auth_info.get('username', '').encode('utf-8') + b':' + auth_info.get('password', '').encode('utf-8')) class ResolveAuthTest(base.BaseTestCase): index_config = {'auth': encode_auth({'username': 'indexuser'})} private_config = {'auth': encode_auth({'username': 'privateuser'})} legacy_config = {'auth': encode_auth({'username': 'legacyauth'})} auth_config = parse_auth({ 'https://index.docker.io/v1/': index_config, 'my.registry.net': private_config, 'http://legacy.registry.url/v1/': legacy_config, }) def test_resolve_authconfig_hostname_only(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'my.registry.net' )['username'], 'privateuser' ) def test_resolve_authconfig_no_protocol(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'my.registry.net/v1/' )['username'], 'privateuser' ) def test_resolve_authconfig_no_path(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'http://my.registry.net' )['username'], 'privateuser' ) def test_resolve_authconfig_no_path_trailing_slash(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'http://my.registry.net/' )['username'], 'privateuser' ) def test_resolve_authconfig_no_path_wrong_secure_proto(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'https://my.registry.net' )['username'], 'privateuser' ) def test_resolve_authconfig_no_path_wrong_insecure_proto(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'http://index.docker.io' )['username'], 'indexuser' ) def test_resolve_authconfig_path_wrong_proto(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'https://my.registry.net/v1/' )['username'], 'privateuser' ) def test_resolve_authconfig_default_registry(self): self.assertEqual( auth.resolve_authconfig(self.auth_config)['username'], 'indexuser' ) def test_resolve_authconfig_default_explicit_none(self): self.assertEqual( auth.resolve_authconfig(self.auth_config, None)['username'], 'indexuser' ) def test_resolve_authconfig_fully_explicit(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'http://my.registry.net/v1/' )['username'], 'privateuser' ) def test_resolve_authconfig_legacy_config(self): self.assertEqual( auth.resolve_authconfig( self.auth_config, 'legacy.registry.url' )['username'], 'legacyauth' ) def test_resolve_authconfig_no_match(self): self.assertTrue( auth.resolve_authconfig(self.auth_config, 'does.not.exist') is None ) def test_resolve_registry_and_auth_library_image(self): image = 'image' self.assertEqual( auth.resolve_authconfig( self.auth_config, auth.resolve_repository_name(image)[0] )['username'], 'indexuser', ) def test_resolve_registry_and_auth_hub_image(self): image = 'username/image' self.assertEqual( auth.resolve_authconfig( self.auth_config, auth.resolve_repository_name(image)[0] )['username'], 'indexuser', ) def test_resolve_registry_and_auth_explicit_hub(self): image = 'docker.io/username/image' self.assertEqual( auth.resolve_authconfig( self.auth_config, auth.resolve_repository_name(image)[0] )['username'], 'indexuser', ) def test_resolve_registry_and_auth_explicit_legacy_hub(self): image = 'index.docker.io/username/image' self.assertEqual( auth.resolve_authconfig( self.auth_config, auth.resolve_repository_name(image)[0] )['username'], 'indexuser', ) def test_resolve_registry_and_auth_private_registry(self): image = 'my.registry.net/image' self.assertEqual( auth.resolve_authconfig( self.auth_config, auth.resolve_repository_name(image)[0] )['username'], 'privateuser', ) def test_resolve_registry_and_auth_unauthenticated_registry(self): image = 'other.registry.net/image' self.assertEqual( auth.resolve_authconfig( self.auth_config, auth.resolve_repository_name(image)[0] ), None, ) class LoadConfigTest(base.Cleanup, base.BaseTestCase): def test_load_config_no_file(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) cfg = auth.load_config(folder) self.assertTrue(cfg is not None) def test_load_config(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, '.dockercfg') with open(dockercfg_path, 'w') as f: auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') f.write('auth = {0}\n'.format(auth_)) f.write('email = sakuya@scarlet.net') cfg = auth.load_config(dockercfg_path) assert auth.INDEX_NAME in cfg self.assertNotEqual(cfg[auth.INDEX_NAME], None) cfg = cfg[auth.INDEX_NAME] self.assertEqual(cfg['username'], 'sakuya') self.assertEqual(cfg['password'], 'izayoi') self.assertEqual(cfg['email'], 'sakuya@scarlet.net') self.assertEqual(cfg.get('auth'), None) def test_load_config_with_random_name(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, '.{0}.dockercfg'.format( random.randrange(100000))) registry = 'https://your.private.registry.io' auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') config = { registry: { 'auth': '{0}'.format(auth_), 'email': 'sakuya@scarlet.net' } } with open(dockercfg_path, 'w') as f: json.dump(config, f) cfg = auth.load_config(dockercfg_path) assert registry in cfg self.assertNotEqual(cfg[registry], None) cfg = cfg[registry] self.assertEqual(cfg['username'], 'sakuya') self.assertEqual(cfg['password'], 'izayoi') self.assertEqual(cfg['email'], 'sakuya@scarlet.net') self.assertEqual(cfg.get('auth'), None) def test_load_config_custom_config_env(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, 'config.json') registry = 'https://your.private.registry.io' auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') config = { registry: { 'auth': '{0}'.format(auth_), 'email': 'sakuya@scarlet.net' } } with open(dockercfg_path, 'w') as f: json.dump(config, f) with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): cfg = auth.load_config(None) assert registry in cfg self.assertNotEqual(cfg[registry], None) cfg = cfg[registry] self.assertEqual(cfg['username'], 'sakuya') self.assertEqual(cfg['password'], 'izayoi') self.assertEqual(cfg['email'], 'sakuya@scarlet.net') self.assertEqual(cfg.get('auth'), None) def test_load_config_custom_config_env_with_auths(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, 'config.json') registry = 'https://your.private.registry.io' auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') config = { 'auths': { registry: { 'auth': '{0}'.format(auth_), 'email': 'sakuya@scarlet.net' } } } with open(dockercfg_path, 'w') as f: json.dump(config, f) with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): cfg = auth.load_config(None) assert registry in cfg self.assertNotEqual(cfg[registry], None) cfg = cfg[registry] self.assertEqual(cfg['username'], 'sakuya') self.assertEqual(cfg['password'], 'izayoi') self.assertEqual(cfg['email'], 'sakuya@scarlet.net') self.assertEqual(cfg.get('auth'), None) def test_load_config_custom_config_env_utf8(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, 'config.json') registry = 'https://your.private.registry.io' auth_ = base64.b64encode( b'sakuya\xc3\xa6:izayoi\xc3\xa6').decode('ascii') config = { 'auths': { registry: { 'auth': '{0}'.format(auth_), 'email': 'sakuya@scarlet.net' } } } with open(dockercfg_path, 'w') as f: json.dump(config, f) with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): cfg = auth.load_config(None) assert registry in cfg self.assertNotEqual(cfg[registry], None) cfg = cfg[registry] self.assertEqual(cfg['username'], b'sakuya\xc3\xa6'.decode('utf8')) self.assertEqual(cfg['password'], b'izayoi\xc3\xa6'.decode('utf8')) self.assertEqual(cfg['email'], 'sakuya@scarlet.net') self.assertEqual(cfg.get('auth'), None) def test_load_config_custom_config_env_with_headers(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, 'config.json') config = { 'HttpHeaders': { 'Name': 'Spike', 'Surname': 'Spiegel' }, } with open(dockercfg_path, 'w') as f: json.dump(config, f) with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): cfg = auth.load_config(None) assert 'HttpHeaders' in cfg self.assertNotEqual(cfg['HttpHeaders'], None) cfg = cfg['HttpHeaders'] self.assertEqual(cfg['Name'], 'Spike') self.assertEqual(cfg['Surname'], 'Spiegel') def test_load_config_unknown_keys(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, 'config.json') config = { 'detachKeys': 'ctrl-q, ctrl-u, ctrl-i' } with open(dockercfg_path, 'w') as f: json.dump(config, f) cfg = auth.load_config(dockercfg_path) assert cfg == {} def test_load_config_invalid_auth_dict(self): folder = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, 'config.json') config = { 'auths': { 'scarlet.net': {'sakuya': 'izayoi'} } } with open(dockercfg_path, 'w') as f: json.dump(config, f) cfg = auth.load_config(dockercfg_path) assert cfg == {} docker-py-1.8.0/tests/unit/build_test.py0000644000175000017500000000645612702731056016710 0ustar kickkickimport gzip import io import docker from .api_test import DockerClientTest class BuildTest(DockerClientTest): def test_build_container(self): script = io.BytesIO('\n'.join([ 'FROM busybox', 'MAINTAINER docker-py', 'RUN mkdir -p /tmp/test', 'EXPOSE 8080', 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' ' /tmp/silence.tar.gz' ]).encode('ascii')) self.client.build(fileobj=script) def test_build_container_pull(self): script = io.BytesIO('\n'.join([ 'FROM busybox', 'MAINTAINER docker-py', 'RUN mkdir -p /tmp/test', 'EXPOSE 8080', 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' ' /tmp/silence.tar.gz' ]).encode('ascii')) self.client.build(fileobj=script, pull=True) def test_build_container_stream(self): script = io.BytesIO('\n'.join([ 'FROM busybox', 'MAINTAINER docker-py', 'RUN mkdir -p /tmp/test', 'EXPOSE 8080', 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' ' /tmp/silence.tar.gz' ]).encode('ascii')) self.client.build(fileobj=script, stream=True) def test_build_container_custom_context(self): script = io.BytesIO('\n'.join([ 'FROM busybox', 'MAINTAINER docker-py', 'RUN mkdir -p /tmp/test', 'EXPOSE 8080', 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' ' /tmp/silence.tar.gz' ]).encode('ascii')) context = docker.utils.mkbuildcontext(script) self.client.build(fileobj=context, custom_context=True) def test_build_container_custom_context_gzip(self): script = io.BytesIO('\n'.join([ 'FROM busybox', 'MAINTAINER docker-py', 'RUN mkdir -p /tmp/test', 'EXPOSE 8080', 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' ' /tmp/silence.tar.gz' ]).encode('ascii')) context = docker.utils.mkbuildcontext(script) gz_context = gzip.GzipFile(fileobj=context) self.client.build( fileobj=gz_context, custom_context=True, encoding="gzip" ) def test_build_remote_with_registry_auth(self): self.client._auth_configs = { 'https://example.com': { 'user': 'example', 'password': 'example', 'email': 'example@example.com' } } self.client.build(path='https://github.com/docker-library/mongo') def test_build_container_with_named_dockerfile(self): self.client.build('.', dockerfile='nameddockerfile') def test_build_container_with_container_limits(self): self.client.build('.', container_limits={ 'memory': 1024 * 1024, 'cpusetcpus': 1, 'cpushares': 1000, 'memswap': 1024 * 1024 * 8 }) def test_build_container_invalid_container_limits(self): self.assertRaises( docker.errors.DockerException, lambda: self.client.build('.', container_limits={ 'foo': 'bar' }) ) docker-py-1.8.0/tests/unit/client_test.py0000644000175000017500000000145112702731056017055 0ustar kickkickimport os from docker.client import Client from .. import base TEST_CERT_DIR = os.path.join( os.path.dirname(__file__), 'testdata/certs', ) class ClientTest(base.BaseTestCase): def setUp(self): self.os_environ = os.environ.copy() def tearDown(self): os.environ = self.os_environ def test_from_env(self): """Test that environment variables are passed through to utils.kwargs_from_env(). KwargsFromEnvTest tests that environment variables are parsed correctly.""" os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', DOCKER_CERT_PATH=TEST_CERT_DIR, DOCKER_TLS_VERIFY='1') client = Client.from_env() self.assertEqual(client.base_url, "https://192.168.59.103:2376") docker-py-1.8.0/tests/unit/container_test.py0000644000175000017500000015442612702731056017574 0ustar kickkickimport datetime import json import signal import docker import pytest import six from . import fake_api from ..base import requires_api_version from .api_test import ( DockerClientTest, url_prefix, fake_request, DEFAULT_TIMEOUT_SECONDS, fake_inspect_container ) try: from unittest import mock except ImportError: import mock def fake_inspect_container_tty(self, container): return fake_inspect_container(self, container, tty=True) class StartContainerTest(DockerClientTest): def test_start_container(self): self.client.start(fake_api.FAKE_CONTAINER_ID) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'containers/3cc2351ab11b/start' ) self.assertEqual(json.loads(args[1]['data']), {}) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_start_container_none(self): with pytest.raises(ValueError) as excinfo: self.client.start(container=None) self.assertEqual( str(excinfo.value), 'image or container param is undefined', ) with pytest.raises(ValueError) as excinfo: self.client.start(None) self.assertEqual( str(excinfo.value), 'image or container param is undefined', ) def test_start_container_regression_573(self): self.client.start(**{'container': fake_api.FAKE_CONTAINER_ID}) def test_start_container_with_lxc_conf(self): def call_start(): self.client.start( fake_api.FAKE_CONTAINER_ID, lxc_conf={'lxc.conf.k': 'lxc.conf.value'} ) pytest.deprecated_call(call_start) def test_start_container_with_lxc_conf_compat(self): def call_start(): self.client.start( fake_api.FAKE_CONTAINER_ID, lxc_conf=[{'Key': 'lxc.conf.k', 'Value': 'lxc.conf.value'}] ) pytest.deprecated_call(call_start) def test_start_container_with_binds_ro(self): def call_start(): self.client.start( fake_api.FAKE_CONTAINER_ID, binds={ '/tmp': { "bind": '/mnt', "ro": True } } ) pytest.deprecated_call(call_start) def test_start_container_with_binds_rw(self): def call_start(): self.client.start( fake_api.FAKE_CONTAINER_ID, binds={ '/tmp': {"bind": '/mnt', "ro": False} } ) pytest.deprecated_call(call_start) def test_start_container_with_port_binds(self): self.maxDiff = None def call_start(): self.client.start(fake_api.FAKE_CONTAINER_ID, port_bindings={ 1111: None, 2222: 2222, '3333/udp': (3333,), 4444: ('127.0.0.1',), 5555: ('127.0.0.1', 5555), 6666: [('127.0.0.1',), ('192.168.0.1',)] }) pytest.deprecated_call(call_start) def test_start_container_with_links(self): def call_start(): self.client.start( fake_api.FAKE_CONTAINER_ID, links={'path': 'alias'} ) pytest.deprecated_call(call_start) def test_start_container_with_multiple_links(self): def call_start(): self.client.start( fake_api.FAKE_CONTAINER_ID, links={ 'path1': 'alias1', 'path2': 'alias2' } ) pytest.deprecated_call(call_start) def test_start_container_with_links_as_list_of_tuples(self): def call_start(): self.client.start(fake_api.FAKE_CONTAINER_ID, links=[('path', 'alias')]) pytest.deprecated_call(call_start) def test_start_container_privileged(self): def call_start(): self.client.start(fake_api.FAKE_CONTAINER_ID, privileged=True) pytest.deprecated_call(call_start) def test_start_container_with_dict_instead_of_id(self): self.client.start({'Id': fake_api.FAKE_CONTAINER_ID}) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'containers/3cc2351ab11b/start' ) self.assertEqual(json.loads(args[1]['data']), {}) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) class CreateContainerTest(DockerClientTest): def test_create_container(self): self.client.create_container('busybox', 'true') args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["true"], "AttachStdin": false, "AttachStderr": true, "AttachStdout": true, "StdinOnce": false, "OpenStdin": false, "NetworkDisabled": false}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_binds(self): mount_dest = '/mnt' self.client.create_container('busybox', ['ls', mount_dest], volumes=[mount_dest]) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["ls", "/mnt"], "AttachStdin": false, "Volumes": {"/mnt": {}}, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_volume_string(self): mount_dest = '/mnt' self.client.create_container('busybox', ['ls', mount_dest], volumes=mount_dest) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["ls", "/mnt"], "AttachStdin": false, "Volumes": {"/mnt": {}}, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_ports(self): self.client.create_container('busybox', 'ls', ports=[1111, (2222, 'udp'), (3333,)]) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["ls"], "AttachStdin": false, "ExposedPorts": { "1111/tcp": {}, "2222/udp": {}, "3333/tcp": {} }, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_entrypoint(self): self.client.create_container('busybox', 'hello', entrypoint='cowsay entry') args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["hello"], "AttachStdin": false, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false, "Entrypoint": ["cowsay", "entry"]}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_cpu_shares(self): self.client.create_container('busybox', 'ls', cpu_shares=5) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["ls"], "AttachStdin": false, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false, "CpuShares": 5}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_cpuset(self): self.client.create_container('busybox', 'ls', cpuset='0,1') args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["ls"], "AttachStdin": false, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false, "Cpuset": "0,1", "CpusetCpus": "0,1"}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_cgroup_parent(self): self.client.create_container( 'busybox', 'ls', host_config=self.client.create_host_config( cgroup_parent='test' ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') data = json.loads(args[1]['data']) self.assertIn('HostConfig', data) self.assertIn('CgroupParent', data['HostConfig']) self.assertEqual(data['HostConfig']['CgroupParent'], 'test') def test_create_container_with_working_dir(self): self.client.create_container('busybox', 'ls', working_dir='/root') args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["ls"], "AttachStdin": false, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false, "WorkingDir": "/root"}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_stdin_open(self): self.client.create_container('busybox', 'true', stdin_open=True) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["true"], "AttachStdin": true, "AttachStderr": true, "AttachStdout": true, "StdinOnce": true, "OpenStdin": true, "NetworkDisabled": false}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_with_volumes_from(self): vol_names = ['foo', 'bar'] try: self.client.create_container('busybox', 'true', volumes_from=vol_names) except docker.errors.DockerException: self.assertTrue( docker.utils.compare_version('1.10', self.client._version) >= 0 ) return args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data'])['VolumesFrom'], ','.join(vol_names)) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_create_container_empty_volumes_from(self): self.client.create_container('busybox', 'true', volumes_from=[]) args = fake_request.call_args data = json.loads(args[1]['data']) self.assertTrue('VolumesFrom' not in data) def test_create_named_container(self): self.client.create_container('busybox', 'true', name='marisa-kirisame') args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["true"], "AttachStdin": false, "AttachStderr": true, "AttachStdout": true, "StdinOnce": false, "OpenStdin": false, "NetworkDisabled": false}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual(args[1]['params'], {'name': 'marisa-kirisame'}) def test_create_container_with_mem_limit_as_int(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( mem_limit=128.0 ) ) args = fake_request.call_args data = json.loads(args[1]['data']) self.assertEqual(data['HostConfig']['Memory'], 128.0) def test_create_container_with_mem_limit_as_string(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( mem_limit='128' ) ) args = fake_request.call_args data = json.loads(args[1]['data']) self.assertEqual(data['HostConfig']['Memory'], 128.0) def test_create_container_with_mem_limit_as_string_with_k_unit(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( mem_limit='128k' ) ) args = fake_request.call_args data = json.loads(args[1]['data']) self.assertEqual(data['HostConfig']['Memory'], 128.0 * 1024) def test_create_container_with_mem_limit_as_string_with_m_unit(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( mem_limit='128m' ) ) args = fake_request.call_args data = json.loads(args[1]['data']) self.assertEqual(data['HostConfig']['Memory'], 128.0 * 1024 * 1024) def test_create_container_with_mem_limit_as_string_with_g_unit(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( mem_limit='128g' ) ) args = fake_request.call_args data = json.loads(args[1]['data']) self.assertEqual( data['HostConfig']['Memory'], 128.0 * 1024 * 1024 * 1024 ) def test_create_container_with_mem_limit_as_string_with_wrong_value(self): self.assertRaises( docker.errors.DockerException, self.client.create_host_config, mem_limit='128p' ) self.assertRaises( docker.errors.DockerException, self.client.create_host_config, mem_limit='1f28' ) def test_create_container_with_lxc_conf(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( lxc_conf={'lxc.conf.k': 'lxc.conf.value'} ) ) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'containers/create' ) expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['LxcConf'] = [ {"Value": "lxc.conf.value", "Key": "lxc.conf.k"} ] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_lxc_conf_compat(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( lxc_conf=[{'Key': 'lxc.conf.k', 'Value': 'lxc.conf.value'}] ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['LxcConf'] = [ {"Value": "lxc.conf.value", "Key": "lxc.conf.k"} ] self.assertEqual( json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_binds_ro(self): mount_dest = '/mnt' mount_origin = '/tmp' self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( binds={mount_origin: { "bind": mount_dest, "ro": True }} ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:ro"] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_binds_rw(self): mount_dest = '/mnt' mount_origin = '/tmp' self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( binds={mount_origin: { "bind": mount_dest, "ro": False }} ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:rw"] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_binds_mode(self): mount_dest = '/mnt' mount_origin = '/tmp' self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( binds={mount_origin: { "bind": mount_dest, "mode": "z", }} ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:z"] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_binds_mode_and_ro_error(self): with pytest.raises(ValueError): mount_dest = '/mnt' mount_origin = '/tmp' self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( binds={mount_origin: { "bind": mount_dest, "mode": "z", "ro": True, }} ) ) def test_create_container_with_binds_list(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( binds=[ "/tmp:/mnt/1:ro", "/tmp:/mnt/2", ], ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Binds'] = [ "/tmp:/mnt/1:ro", "/tmp:/mnt/2", ] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_port_binds(self): self.maxDiff = None self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( port_bindings={ 1111: None, 2222: 2222, '3333/udp': (3333,), 4444: ('127.0.0.1',), 5555: ('127.0.0.1', 5555), 6666: [('127.0.0.1',), ('192.168.0.1',)] } ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') data = json.loads(args[1]['data']) port_bindings = data['HostConfig']['PortBindings'] self.assertTrue('1111/tcp' in port_bindings) self.assertTrue('2222/tcp' in port_bindings) self.assertTrue('3333/udp' in port_bindings) self.assertTrue('4444/tcp' in port_bindings) self.assertTrue('5555/tcp' in port_bindings) self.assertTrue('6666/tcp' in port_bindings) self.assertEqual( [{"HostPort": "", "HostIp": ""}], port_bindings['1111/tcp'] ) self.assertEqual( [{"HostPort": "2222", "HostIp": ""}], port_bindings['2222/tcp'] ) self.assertEqual( [{"HostPort": "3333", "HostIp": ""}], port_bindings['3333/udp'] ) self.assertEqual( [{"HostPort": "", "HostIp": "127.0.0.1"}], port_bindings['4444/tcp'] ) self.assertEqual( [{"HostPort": "5555", "HostIp": "127.0.0.1"}], port_bindings['5555/tcp'] ) self.assertEqual(len(port_bindings['6666/tcp']), 2) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_mac_address(self): mac_address_expected = "02:42:ac:11:00:0a" container = self.client.create_container( 'busybox', ['sleep', '60'], mac_address=mac_address_expected) res = self.client.inspect_container(container['Id']) self.assertEqual(mac_address_expected, res['NetworkSettings']['MacAddress']) def test_create_container_with_links(self): link_path = 'path' alias = 'alias' self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( links={link_path: alias} ) ) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'containers/create' ) expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Links'] = ['path:alias'] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) def test_create_container_with_multiple_links(self): link_path = 'path' alias = 'alias' self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( links={ link_path + '1': alias + '1', link_path + '2': alias + '2' } ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Links'] = [ 'path1:alias1', 'path2:alias2' ] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) def test_create_container_with_links_as_list_of_tuples(self): link_path = 'path' alias = 'alias' self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( links=[(link_path, alias)] ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Links'] = ['path:alias'] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) def test_create_container_privileged(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config(privileged=True) ) expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Privileged'] = True args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_restart_policy(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( restart_policy={ "Name": "always", "MaximumRetryCount": 0 } ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['RestartPolicy'] = { "MaximumRetryCount": 0, "Name": "always" } self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_added_capabilities(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config(cap_add=['MKNOD']) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['CapAdd'] = ['MKNOD'] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_dropped_capabilities(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config(cap_drop=['MKNOD']) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['CapDrop'] = ['MKNOD'] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_devices(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( devices=['/dev/sda:/dev/xvda:rwm', '/dev/sdb:/dev/xvdb', '/dev/sdc'] ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Devices'] = [ {'CgroupPermissions': 'rwm', 'PathInContainer': '/dev/xvda', 'PathOnHost': '/dev/sda'}, {'CgroupPermissions': 'rwm', 'PathInContainer': '/dev/xvdb', 'PathOnHost': '/dev/sdb'}, {'CgroupPermissions': 'rwm', 'PathInContainer': '/dev/sdc', 'PathOnHost': '/dev/sdc'} ] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_labels_dict(self): labels_dict = { six.text_type('foo'): six.text_type('1'), six.text_type('bar'): six.text_type('2'), } self.client.create_container( 'busybox', 'true', labels=labels_dict, ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data'])['Labels'], labels_dict) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_labels_list(self): labels_list = [ six.text_type('foo'), six.text_type('bar'), ] labels_dict = { six.text_type('foo'): six.text_type(), six.text_type('bar'): six.text_type(), } self.client.create_container( 'busybox', 'true', labels=labels_list, ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data'])['Labels'], labels_dict) self.assertEqual( args[1]['headers'], {'Content-Type': 'application/json'} ) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_named_volume(self): mount_dest = '/mnt' volume_name = 'name' self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( binds={volume_name: { "bind": mount_dest, "ro": False }}), volume_driver='foodriver', ) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'containers/create' ) expected_payload = self.base_create_payload() expected_payload['VolumeDriver'] = 'foodriver' expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Binds'] = ["name:/mnt:rw"] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) def test_create_container_with_stop_signal(self): self.client.create_container('busybox', 'ls', stop_signal='SIGINT') args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["ls"], "AttachStdin": false, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false, "StopSignal": "SIGINT"}''')) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) @requires_api_version('1.22') def test_create_container_with_aliases(self): self.client.create_container( 'busybox', 'ls', host_config=self.client.create_host_config( network_mode='some-network', ), networking_config=self.client.create_networking_config({ 'some-network': self.client.create_endpoint_config( aliases=['foo', 'bar'], ), }), ) args = fake_request.call_args self.assertEqual(json.loads(args[1]['data']), json.loads(''' {"Tty": false, "Image": "busybox", "Cmd": ["ls"], "AttachStdin": false, "AttachStderr": true, "AttachStdout": true, "OpenStdin": false, "StdinOnce": false, "NetworkDisabled": false, "HostConfig": { "NetworkMode": "some-network" }, "NetworkingConfig": { "EndpointsConfig": { "some-network": {"Aliases": ["foo", "bar"]} } }}''')) @requires_api_version('1.22') def test_create_container_with_tmpfs_list(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( tmpfs=[ "/tmp", "/mnt:size=3G,uid=100" ] ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Tmpfs'] = { "/tmp": "", "/mnt": "size=3G,uid=100" } self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) @requires_api_version('1.22') def test_create_container_with_tmpfs_dict(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( tmpfs={ "/tmp": "", "/mnt": "size=3G,uid=100" } ) ) args = fake_request.call_args self.assertEqual(args[0][1], url_prefix + 'containers/create') expected_payload = self.base_create_payload() expected_payload['HostConfig'] = self.client.create_host_config() expected_payload['HostConfig']['Tmpfs'] = { "/tmp": "", "/mnt": "size=3G,uid=100" } self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) self.assertEqual( args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS ) class ContainerTest(DockerClientTest): def test_list_containers(self): self.client.containers(all=True) fake_request.assert_called_with( 'GET', url_prefix + 'containers/json', params={ 'all': 1, 'since': None, 'size': 0, 'limit': -1, 'trunc_cmd': 0, 'before': None }, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_resize_container(self): self.client.resize( {'Id': fake_api.FAKE_CONTAINER_ID}, height=15, width=120 ) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/resize', params={'h': 15, 'w': 120}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_rename_container(self): self.client.rename( {'Id': fake_api.FAKE_CONTAINER_ID}, name='foobar' ) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/rename', params={'name': 'foobar'}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_wait(self): self.client.wait(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/wait', timeout=None ) def test_wait_with_dict_instead_of_id(self): self.client.wait({'Id': fake_api.FAKE_CONTAINER_ID}) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/wait', timeout=None ) def test_logs(self): with mock.patch('docker.Client.inspect_container', fake_inspect_container): logs = self.client.logs(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=False ) self.assertEqual( logs, 'Flowering Nights\n(Sakuya Iyazoi)\n'.encode('ascii') ) def test_logs_with_dict_instead_of_id(self): with mock.patch('docker.Client.inspect_container', fake_inspect_container): logs = self.client.logs({'Id': fake_api.FAKE_CONTAINER_ID}) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=False ) self.assertEqual( logs, 'Flowering Nights\n(Sakuya Iyazoi)\n'.encode('ascii') ) def test_log_streaming(self): with mock.patch('docker.Client.inspect_container', fake_inspect_container): self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True, follow=False) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=True ) def test_log_following(self): with mock.patch('docker.Client.inspect_container', fake_inspect_container): self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, follow=True) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=False ) def test_log_following_backwards(self): with mock.patch('docker.Client.inspect_container', fake_inspect_container): self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=True ) def test_log_streaming_and_following(self): with mock.patch('docker.Client.inspect_container', fake_inspect_container): self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True, follow=True) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=True ) def test_log_tail(self): with mock.patch('docker.Client.inspect_container', fake_inspect_container): self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, follow=False, tail=10) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 10}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=False ) def test_log_since(self): ts = 809222400 with mock.patch('docker.Client.inspect_container', fake_inspect_container): self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, follow=False, since=ts) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all', 'since': ts}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=False ) def test_log_since_with_datetime(self): ts = 809222400 time = datetime.datetime.utcfromtimestamp(ts) with mock.patch('docker.Client.inspect_container', fake_inspect_container): self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, follow=False, since=time) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all', 'since': ts}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=False ) def test_log_tty(self): m = mock.Mock() with mock.patch('docker.Client.inspect_container', fake_inspect_container_tty): with mock.patch('docker.Client._stream_raw_result', m): self.client.logs(fake_api.FAKE_CONTAINER_ID, follow=True, stream=True) self.assertTrue(m.called) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/logs', params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=True ) def test_diff(self): self.client.diff(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/changes', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_diff_with_dict_instead_of_id(self): self.client.diff({'Id': fake_api.FAKE_CONTAINER_ID}) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/changes', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_port(self): self.client.port({'Id': fake_api.FAKE_CONTAINER_ID}, 1111) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/json', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_stop_container(self): timeout = 2 self.client.stop(fake_api.FAKE_CONTAINER_ID, timeout=timeout) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/stop', params={'t': timeout}, timeout=(DEFAULT_TIMEOUT_SECONDS + timeout) ) def test_stop_container_with_dict_instead_of_id(self): timeout = 2 self.client.stop({'Id': fake_api.FAKE_CONTAINER_ID}, timeout=timeout) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/stop', params={'t': timeout}, timeout=(DEFAULT_TIMEOUT_SECONDS + timeout) ) def test_pause_container(self): self.client.pause(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/pause', timeout=(DEFAULT_TIMEOUT_SECONDS) ) def test_unpause_container(self): self.client.unpause(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/unpause', timeout=(DEFAULT_TIMEOUT_SECONDS) ) def test_kill_container(self): self.client.kill(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/kill', params={}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_kill_container_with_dict_instead_of_id(self): self.client.kill({'Id': fake_api.FAKE_CONTAINER_ID}) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/kill', params={}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_kill_container_with_signal(self): self.client.kill(fake_api.FAKE_CONTAINER_ID, signal=signal.SIGTERM) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/kill', params={'signal': signal.SIGTERM}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_restart_container(self): self.client.restart(fake_api.FAKE_CONTAINER_ID, timeout=2) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/restart', params={'t': 2}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_restart_container_with_dict_instead_of_id(self): self.client.restart({'Id': fake_api.FAKE_CONTAINER_ID}, timeout=2) fake_request.assert_called_with( 'POST', url_prefix + 'containers/3cc2351ab11b/restart', params={'t': 2}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_remove_container(self): self.client.remove_container(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'DELETE', url_prefix + 'containers/3cc2351ab11b', params={'v': False, 'link': False, 'force': False}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_remove_container_with_dict_instead_of_id(self): self.client.remove_container({'Id': fake_api.FAKE_CONTAINER_ID}) fake_request.assert_called_with( 'DELETE', url_prefix + 'containers/3cc2351ab11b', params={'v': False, 'link': False, 'force': False}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_export(self): self.client.export(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/export', stream=True, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_export_with_dict_instead_of_id(self): self.client.export({'Id': fake_api.FAKE_CONTAINER_ID}) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/export', stream=True, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_inspect_container(self): self.client.inspect_container(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/json', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_inspect_container_undefined_id(self): for arg in None, '', {True: True}: with pytest.raises(docker.errors.NullResource) as excinfo: self.client.inspect_container(arg) self.assertEqual( excinfo.value.args[0], 'image or container param is undefined' ) def test_container_stats(self): self.client.stats(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/stats', timeout=60, stream=True ) def test_container_top(self): self.client.top(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/top', params={}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_container_top_with_psargs(self): self.client.top(fake_api.FAKE_CONTAINER_ID, 'waux') fake_request.assert_called_with( 'GET', url_prefix + 'containers/3cc2351ab11b/top', params={'ps_args': 'waux'}, timeout=DEFAULT_TIMEOUT_SECONDS ) @requires_api_version('1.22') def test_container_update(self): self.client.update_container( fake_api.FAKE_CONTAINER_ID, mem_limit='2k', cpu_shares=124, blkio_weight=345 ) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'containers/3cc2351ab11b/update' ) self.assertEqual( json.loads(args[1]['data']), {'Memory': 2 * 1024, 'CpuShares': 124, 'BlkioWeight': 345} ) self.assertEqual( args[1]['headers']['Content-Type'], 'application/json' ) docker-py-1.8.0/tests/unit/exec_test.py0000644000175000017500000000415612702731056016530 0ustar kickkickimport json from . import fake_api from .api_test import ( DockerClientTest, url_prefix, fake_request, DEFAULT_TIMEOUT_SECONDS, ) class ExecTest(DockerClientTest): def test_exec_create(self): self.client.exec_create(fake_api.FAKE_CONTAINER_ID, ['ls', '-1']) args = fake_request.call_args self.assertEqual( 'POST', args[0][0], url_prefix + 'containers/{0}/exec'.format( fake_api.FAKE_CONTAINER_ID ) ) self.assertEqual( json.loads(args[1]['data']), { 'Tty': False, 'AttachStdout': True, 'Container': fake_api.FAKE_CONTAINER_ID, 'Cmd': ['ls', '-1'], 'Privileged': False, 'AttachStdin': False, 'AttachStderr': True, 'User': '' } ) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_exec_start(self): self.client.exec_start(fake_api.FAKE_EXEC_ID) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'exec/{0}/start'.format( fake_api.FAKE_EXEC_ID ) ) self.assertEqual( json.loads(args[1]['data']), { 'Tty': False, 'Detach': False, } ) self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) def test_exec_inspect(self): self.client.exec_inspect(fake_api.FAKE_EXEC_ID) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'exec/{0}/json'.format( fake_api.FAKE_EXEC_ID ) ) def test_exec_resize(self): self.client.exec_resize(fake_api.FAKE_EXEC_ID, height=20, width=60) fake_request.assert_called_with( 'POST', url_prefix + 'exec/{0}/resize'.format(fake_api.FAKE_EXEC_ID), params={'h': 20, 'w': 60}, timeout=DEFAULT_TIMEOUT_SECONDS ) docker-py-1.8.0/tests/unit/fake_api.py0000644000175000017500000003561212702731056016305 0ustar kickkick# Copyright 2013 dotCloud inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from . import fake_stat from docker import constants CURRENT_VERSION = 'v{0}'.format(constants.DEFAULT_DOCKER_API_VERSION) FAKE_CONTAINER_ID = '3cc2351ab11b' FAKE_IMAGE_ID = 'e9aa60c60128' FAKE_EXEC_ID = 'd5d177f121dc' FAKE_IMAGE_NAME = 'test_image' FAKE_TARBALL_PATH = '/path/to/tarball' FAKE_REPO_NAME = 'repo' FAKE_TAG_NAME = 'tag' FAKE_FILE_NAME = 'file' FAKE_URL = 'myurl' FAKE_PATH = '/path' FAKE_VOLUME_NAME = 'perfectcherryblossom' # Each method is prefixed with HTTP method (get, post...) # for clarity and readability def get_fake_raw_version(): status_code = 200 response = { "ApiVersion": "1.18", "GitCommit": "fake-commit", "GoVersion": "go1.3.3", "Version": "1.5.0" } return status_code, response def get_fake_version(): status_code = 200 response = {'GoVersion': '1', 'Version': '1.1.1', 'GitCommit': 'deadbeef+CHANGES'} return status_code, response def get_fake_info(): status_code = 200 response = {'Containers': 1, 'Images': 1, 'Debug': False, 'MemoryLimit': False, 'SwapLimit': False, 'IPv4Forwarding': True} return status_code, response def get_fake_search(): status_code = 200 response = [{'Name': 'busybox', 'Description': 'Fake Description'}] return status_code, response def get_fake_images(): status_code = 200 response = [{ 'Id': FAKE_IMAGE_ID, 'Created': '2 days ago', 'Repository': 'busybox', 'RepoTags': ['busybox:latest', 'busybox:1.0'], }] return status_code, response def get_fake_image_history(): status_code = 200 response = [ { "Id": "b750fe79269d", "Created": 1364102658, "CreatedBy": "/bin/bash" }, { "Id": "27cf78414709", "Created": 1364068391, "CreatedBy": "" } ] return status_code, response def post_fake_import_image(): status_code = 200 response = 'Import messages...' return status_code, response def get_fake_containers(): status_code = 200 response = [{ 'Id': FAKE_CONTAINER_ID, 'Image': 'busybox:latest', 'Created': '2 days ago', 'Command': 'true', 'Status': 'fake status' }] return status_code, response def post_fake_start_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_resize_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_create_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def get_fake_inspect_container(tty=False): status_code = 200 response = { 'Id': FAKE_CONTAINER_ID, 'Config': {'Privileged': True, 'Tty': tty}, 'ID': FAKE_CONTAINER_ID, 'Image': 'busybox:latest', "State": { "Running": True, "Pid": 0, "ExitCode": 0, "StartedAt": "2013-09-25T14:01:18.869545111+02:00", "Ghost": False }, "MacAddress": "02:42:ac:11:00:0a" } return status_code, response def get_fake_inspect_image(): status_code = 200 response = { 'id': FAKE_IMAGE_ID, 'parent': "27cf784147099545", 'created': "2013-03-23T22:24:18.818426-07:00", 'container': FAKE_CONTAINER_ID, 'container_config': { "Hostname": "", "User": "", "Memory": 0, "MemorySwap": 0, "AttachStdin": False, "AttachStdout": False, "AttachStderr": False, "PortSpecs": "", "Tty": True, "OpenStdin": True, "StdinOnce": False, "Env": "", "Cmd": ["/bin/bash"], "Dns": "", "Image": "base", "Volumes": "", "VolumesFrom": "", "WorkingDir": "" }, 'Size': 6823592 } return status_code, response def get_fake_port(): status_code = 200 response = { 'HostConfig': { 'Binds': None, 'ContainerIDFile': '', 'Links': None, 'LxcConf': None, 'PortBindings': { '1111': None, '1111/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '4567'}], '2222': None }, 'Privileged': False, 'PublishAllPorts': False }, 'NetworkSettings': { 'Bridge': 'docker0', 'PortMapping': None, 'Ports': { '1111': None, '1111/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '4567'}], '2222': None}, 'MacAddress': '02:42:ac:11:00:0a' } } return status_code, response def get_fake_insert_image(): status_code = 200 response = {'StatusCode': 0} return status_code, response def get_fake_wait(): status_code = 200 response = {'StatusCode': 0} return status_code, response def get_fake_logs(): status_code = 200 response = (b'\x01\x00\x00\x00\x00\x00\x00\x11Flowering Nights\n' b'\x01\x00\x00\x00\x00\x00\x00\x10(Sakuya Iyazoi)\n') return status_code, response def get_fake_diff(): status_code = 200 response = [{'Path': '/test', 'Kind': 1}] return status_code, response def get_fake_events(): status_code = 200 response = [{'status': 'stop', 'id': FAKE_CONTAINER_ID, 'from': FAKE_IMAGE_ID, 'time': 1423247867}] return status_code, response def get_fake_export(): status_code = 200 response = 'Byte Stream....' return status_code, response def post_fake_exec_create(): status_code = 200 response = {'Id': FAKE_EXEC_ID} return status_code, response def post_fake_exec_start(): status_code = 200 response = (b'\x01\x00\x00\x00\x00\x00\x00\x11bin\nboot\ndev\netc\n' b'\x01\x00\x00\x00\x00\x00\x00\x12lib\nmnt\nproc\nroot\n' b'\x01\x00\x00\x00\x00\x00\x00\x0csbin\nusr\nvar\n') return status_code, response def post_fake_exec_resize(): status_code = 201 return status_code, '' def get_fake_exec_inspect(): return 200, { 'OpenStderr': True, 'OpenStdout': True, 'Container': get_fake_inspect_container()[1], 'Running': False, 'ProcessConfig': { 'arguments': ['hello world'], 'tty': False, 'entrypoint': 'echo', 'privileged': False, 'user': '' }, 'ExitCode': 0, 'ID': FAKE_EXEC_ID, 'OpenStdin': False } def post_fake_stop_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_kill_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_pause_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_unpause_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_restart_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_rename_container(): status_code = 204 return status_code, None def delete_fake_remove_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_image_create(): status_code = 200 response = {'Id': FAKE_IMAGE_ID} return status_code, response def delete_fake_remove_image(): status_code = 200 response = {'Id': FAKE_IMAGE_ID} return status_code, response def get_fake_get_image(): status_code = 200 response = 'Byte Stream....' return status_code, response def post_fake_load_image(): status_code = 200 response = {'Id': FAKE_IMAGE_ID} return status_code, response def post_fake_commit(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_push(): status_code = 200 response = {'Id': FAKE_IMAGE_ID} return status_code, response def post_fake_build_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response def post_fake_tag_image(): status_code = 200 response = {'Id': FAKE_IMAGE_ID} return status_code, response def get_fake_stats(): status_code = 200 response = fake_stat.OBJ return status_code, response def get_fake_top(): return 200, { 'Processes': [ [ 'root', '26501', '6907', '0', '10:32', 'pts/55', '00:00:00', 'sleep 60', ], ], 'Titles': [ 'UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD', ], } def get_fake_volume_list(): status_code = 200 response = { 'Volumes': [ { 'Name': 'perfectcherryblossom', 'Driver': 'local', 'Mountpoint': '/var/lib/docker/volumes/perfectcherryblossom' }, { 'Name': 'subterraneananimism', 'Driver': 'local', 'Mountpoint': '/var/lib/docker/volumes/subterraneananimism' } ] } return status_code, response def get_fake_volume(): status_code = 200 response = { 'Name': 'perfectcherryblossom', 'Driver': 'local', 'Mountpoint': '/var/lib/docker/volumes/perfectcherryblossom' } return status_code, response def fake_remove_volume(): return 204, None def post_fake_update_container(): return 200, {'Warnings': []} # Maps real api url to fake response callback prefix = 'http+docker://localunixsocket' fake_responses = { '{0}/version'.format(prefix): get_fake_raw_version, '{1}/{0}/version'.format(CURRENT_VERSION, prefix): get_fake_version, '{1}/{0}/info'.format(CURRENT_VERSION, prefix): get_fake_info, '{1}/{0}/images/search'.format(CURRENT_VERSION, prefix): get_fake_search, '{1}/{0}/images/json'.format(CURRENT_VERSION, prefix): get_fake_images, '{1}/{0}/images/test_image/history'.format(CURRENT_VERSION, prefix): get_fake_image_history, '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): post_fake_import_image, '{1}/{0}/containers/json'.format(CURRENT_VERSION, prefix): get_fake_containers, '{1}/{0}/containers/3cc2351ab11b/start'.format(CURRENT_VERSION, prefix): post_fake_start_container, '{1}/{0}/containers/3cc2351ab11b/resize'.format(CURRENT_VERSION, prefix): post_fake_resize_container, '{1}/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION, prefix): get_fake_inspect_container, '{1}/{0}/containers/3cc2351ab11b/rename'.format(CURRENT_VERSION, prefix): post_fake_rename_container, '{1}/{0}/images/e9aa60c60128/tag'.format(CURRENT_VERSION, prefix): post_fake_tag_image, '{1}/{0}/containers/3cc2351ab11b/wait'.format(CURRENT_VERSION, prefix): get_fake_wait, '{1}/{0}/containers/3cc2351ab11b/logs'.format(CURRENT_VERSION, prefix): get_fake_logs, '{1}/{0}/containers/3cc2351ab11b/changes'.format(CURRENT_VERSION, prefix): get_fake_diff, '{1}/{0}/containers/3cc2351ab11b/export'.format(CURRENT_VERSION, prefix): get_fake_export, '{1}/{0}/containers/3cc2351ab11b/update'.format(CURRENT_VERSION, prefix): post_fake_update_container, '{1}/{0}/containers/3cc2351ab11b/exec'.format(CURRENT_VERSION, prefix): post_fake_exec_create, '{1}/{0}/exec/d5d177f121dc/start'.format(CURRENT_VERSION, prefix): post_fake_exec_start, '{1}/{0}/exec/d5d177f121dc/json'.format(CURRENT_VERSION, prefix): get_fake_exec_inspect, '{1}/{0}/exec/d5d177f121dc/resize'.format(CURRENT_VERSION, prefix): post_fake_exec_resize, '{1}/{0}/containers/3cc2351ab11b/stats'.format(CURRENT_VERSION, prefix): get_fake_stats, '{1}/{0}/containers/3cc2351ab11b/top'.format(CURRENT_VERSION, prefix): get_fake_top, '{1}/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION, prefix): post_fake_stop_container, '{1}/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION, prefix): post_fake_kill_container, '{1}/{0}/containers/3cc2351ab11b/pause'.format(CURRENT_VERSION, prefix): post_fake_pause_container, '{1}/{0}/containers/3cc2351ab11b/unpause'.format(CURRENT_VERSION, prefix): post_fake_unpause_container, '{1}/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION, prefix): get_fake_port, '{1}/{0}/containers/3cc2351ab11b/restart'.format(CURRENT_VERSION, prefix): post_fake_restart_container, '{1}/{0}/containers/3cc2351ab11b'.format(CURRENT_VERSION, prefix): delete_fake_remove_container, '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): post_fake_image_create, '{1}/{0}/images/e9aa60c60128'.format(CURRENT_VERSION, prefix): delete_fake_remove_image, '{1}/{0}/images/e9aa60c60128/get'.format(CURRENT_VERSION, prefix): get_fake_get_image, '{1}/{0}/images/load'.format(CURRENT_VERSION, prefix): post_fake_load_image, '{1}/{0}/images/test_image/json'.format(CURRENT_VERSION, prefix): get_fake_inspect_image, '{1}/{0}/images/test_image/insert'.format(CURRENT_VERSION, prefix): get_fake_insert_image, '{1}/{0}/images/test_image/push'.format(CURRENT_VERSION, prefix): post_fake_push, '{1}/{0}/commit'.format(CURRENT_VERSION, prefix): post_fake_commit, '{1}/{0}/containers/create'.format(CURRENT_VERSION, prefix): post_fake_create_container, '{1}/{0}/build'.format(CURRENT_VERSION, prefix): post_fake_build_container, '{1}/{0}/events'.format(CURRENT_VERSION, prefix): get_fake_events, ('{1}/{0}/volumes'.format(CURRENT_VERSION, prefix), 'GET'): get_fake_volume_list, ('{1}/{0}/volumes/create'.format(CURRENT_VERSION, prefix), 'POST'): get_fake_volume, ('{1}/{0}/volumes/{2}'.format( CURRENT_VERSION, prefix, FAKE_VOLUME_NAME ), 'GET'): get_fake_volume, ('{1}/{0}/volumes/{2}'.format( CURRENT_VERSION, prefix, FAKE_VOLUME_NAME ), 'DELETE'): fake_remove_volume, } docker-py-1.8.0/tests/unit/fake_stat.py0000644000175000017500000000725512702731056016511 0ustar kickkickOBJ = { "read": "2015-02-11T19:20:46.667237763+02:00", "network": { "rx_bytes": 567224, "rx_packets": 3773, "rx_errors": 0, "rx_dropped": 0, "tx_bytes": 1176, "tx_packets": 13, "tx_errors": 0, "tx_dropped": 0 }, "cpu_stats": { "cpu_usage": { "total_usage": 157260874053, "percpu_usage": [ 52196306950, 24118413549, 53292684398, 27653469156 ], "usage_in_kernelmode": 37140000000, "usage_in_usermode": 62140000000 }, "system_cpu_usage": 3.0881377e+14, "throttling_data": { "periods": 0, "throttled_periods": 0, "throttled_time": 0 } }, "memory_stats": { "usage": 179314688, "max_usage": 258166784, "stats": { "active_anon": 90804224, "active_file": 2195456, "cache": 3096576, "hierarchical_memory_limit": 1.844674407371e+19, "inactive_anon": 85516288, "inactive_file": 798720, "mapped_file": 2646016, "pgfault": 101034, "pgmajfault": 1207, "pgpgin": 115814, "pgpgout": 75613, "rss": 176218112, "rss_huge": 12582912, "total_active_anon": 90804224, "total_active_file": 2195456, "total_cache": 3096576, "total_inactive_anon": 85516288, "total_inactive_file": 798720, "total_mapped_file": 2646016, "total_pgfault": 101034, "total_pgmajfault": 1207, "total_pgpgin": 115814, "total_pgpgout": 75613, "total_rss": 176218112, "total_rss_huge": 12582912, "total_unevictable": 0, "total_writeback": 0, "unevictable": 0, "writeback": 0 }, "failcnt": 0, "limit": 8039038976 }, "blkio_stats": { "io_service_bytes_recursive": [ { "major": 8, "minor": 0, "op": "Read", "value": 72843264 }, { "major": 8, "minor": 0, "op": "Write", "value": 4096 }, { "major": 8, "minor": 0, "op": "Sync", "value": 4096 }, { "major": 8, "minor": 0, "op": "Async", "value": 72843264 }, { "major": 8, "minor": 0, "op": "Total", "value": 72847360 } ], "io_serviced_recursive": [ { "major": 8, "minor": 0, "op": "Read", "value": 10581 }, { "major": 8, "minor": 0, "op": "Write", "value": 1 }, { "major": 8, "minor": 0, "op": "Sync", "value": 1 }, { "major": 8, "minor": 0, "op": "Async", "value": 10581 }, { "major": 8, "minor": 0, "op": "Total", "value": 10582 } ], "io_queue_recursive": [], "io_service_time_recursive": [], "io_wait_time_recursive": [], "io_merged_recursive": [], "io_time_recursive": [], "sectors_recursive": [] } } docker-py-1.8.0/tests/unit/image_test.py0000644000175000017500000002401512702731056016662 0ustar kickkickimport docker import pytest from . import fake_api from .api_test import ( DockerClientTest, fake_request, DEFAULT_TIMEOUT_SECONDS, url_prefix, fake_resolve_authconfig ) try: from unittest import mock except ImportError: import mock class ImageTest(DockerClientTest): def test_image_viz(self): with pytest.raises(Exception): self.client.images('busybox', viz=True) self.fail('Viz output should not be supported!') def test_images(self): self.client.images(all=True) fake_request.assert_called_with( 'GET', url_prefix + 'images/json', params={'filter': None, 'only_ids': 0, 'all': 1}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_images_quiet(self): self.client.images(all=True, quiet=True) fake_request.assert_called_with( 'GET', url_prefix + 'images/json', params={'filter': None, 'only_ids': 1, 'all': 1}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_image_ids(self): self.client.images(quiet=True) fake_request.assert_called_with( 'GET', url_prefix + 'images/json', params={'filter': None, 'only_ids': 1, 'all': 0}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_images_filters(self): self.client.images(filters={'dangling': True}) fake_request.assert_called_with( 'GET', url_prefix + 'images/json', params={'filter': None, 'only_ids': 0, 'all': 0, 'filters': '{"dangling": ["true"]}'}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_pull(self): self.client.pull('joffrey/test001') args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'images/create' ) self.assertEqual( args[1]['params'], {'tag': None, 'fromImage': 'joffrey/test001'} ) self.assertFalse(args[1]['stream']) def test_pull_stream(self): self.client.pull('joffrey/test001', stream=True) args = fake_request.call_args self.assertEqual( args[0][1], url_prefix + 'images/create' ) self.assertEqual( args[1]['params'], {'tag': None, 'fromImage': 'joffrey/test001'} ) self.assertTrue(args[1]['stream']) def test_commit(self): self.client.commit(fake_api.FAKE_CONTAINER_ID) fake_request.assert_called_with( 'POST', url_prefix + 'commit', data='{}', headers={'Content-Type': 'application/json'}, params={ 'repo': None, 'comment': None, 'tag': None, 'container': '3cc2351ab11b', 'author': None, 'changes': None }, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_remove_image(self): self.client.remove_image(fake_api.FAKE_IMAGE_ID) fake_request.assert_called_with( 'DELETE', url_prefix + 'images/e9aa60c60128', params={'force': False, 'noprune': False}, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_image_history(self): self.client.history(fake_api.FAKE_IMAGE_NAME) fake_request.assert_called_with( 'GET', url_prefix + 'images/test_image/history', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_import_image(self): self.client.import_image( fake_api.FAKE_TARBALL_PATH, repository=fake_api.FAKE_REPO_NAME, tag=fake_api.FAKE_TAG_NAME ) fake_request.assert_called_with( 'POST', url_prefix + 'images/create', params={ 'repo': fake_api.FAKE_REPO_NAME, 'tag': fake_api.FAKE_TAG_NAME, 'fromSrc': fake_api.FAKE_TARBALL_PATH }, data=None, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_import_image_from_bytes(self): stream = (i for i in range(0, 100)) self.client.import_image( stream, repository=fake_api.FAKE_REPO_NAME, tag=fake_api.FAKE_TAG_NAME ) fake_request.assert_called_with( 'POST', url_prefix + 'images/create', params={ 'repo': fake_api.FAKE_REPO_NAME, 'tag': fake_api.FAKE_TAG_NAME, 'fromSrc': '-', }, headers={ 'Content-Type': 'application/tar', }, data=stream, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_import_image_from_image(self): self.client.import_image( image=fake_api.FAKE_IMAGE_NAME, repository=fake_api.FAKE_REPO_NAME, tag=fake_api.FAKE_TAG_NAME ) fake_request.assert_called_with( 'POST', url_prefix + 'images/create', params={ 'repo': fake_api.FAKE_REPO_NAME, 'tag': fake_api.FAKE_TAG_NAME, 'fromImage': fake_api.FAKE_IMAGE_NAME }, data=None, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_inspect_image(self): self.client.inspect_image(fake_api.FAKE_IMAGE_NAME) fake_request.assert_called_with( 'GET', url_prefix + 'images/test_image/json', timeout=DEFAULT_TIMEOUT_SECONDS ) def test_inspect_image_undefined_id(self): for arg in None, '', {True: True}: with pytest.raises(docker.errors.NullResource) as excinfo: self.client.inspect_image(arg) self.assertEqual( excinfo.value.args[0], 'image or container param is undefined' ) def test_insert_image(self): try: self.client.insert(fake_api.FAKE_IMAGE_NAME, fake_api.FAKE_URL, fake_api.FAKE_PATH) except docker.errors.DeprecatedMethod: self.assertTrue( docker.utils.compare_version('1.12', self.client._version) >= 0 ) return fake_request.assert_called_with( 'POST', url_prefix + 'images/test_image/insert', params={ 'url': fake_api.FAKE_URL, 'path': fake_api.FAKE_PATH }, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_push_image(self): with mock.patch('docker.auth.auth.resolve_authconfig', fake_resolve_authconfig): self.client.push(fake_api.FAKE_IMAGE_NAME) fake_request.assert_called_with( 'POST', url_prefix + 'images/test_image/push', params={ 'tag': None }, data='{}', headers={'Content-Type': 'application/json'}, stream=False, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_push_image_with_tag(self): with mock.patch('docker.auth.auth.resolve_authconfig', fake_resolve_authconfig): self.client.push( fake_api.FAKE_IMAGE_NAME, tag=fake_api.FAKE_TAG_NAME ) fake_request.assert_called_with( 'POST', url_prefix + 'images/test_image/push', params={ 'tag': fake_api.FAKE_TAG_NAME, }, data='{}', headers={'Content-Type': 'application/json'}, stream=False, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_push_image_stream(self): with mock.patch('docker.auth.auth.resolve_authconfig', fake_resolve_authconfig): self.client.push(fake_api.FAKE_IMAGE_NAME, stream=True) fake_request.assert_called_with( 'POST', url_prefix + 'images/test_image/push', params={ 'tag': None }, data='{}', headers={'Content-Type': 'application/json'}, stream=True, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_tag_image(self): self.client.tag(fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME) fake_request.assert_called_with( 'POST', url_prefix + 'images/e9aa60c60128/tag', params={ 'tag': None, 'repo': 'repo', 'force': 0 }, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_tag_image_tag(self): self.client.tag( fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME, tag=fake_api.FAKE_TAG_NAME ) fake_request.assert_called_with( 'POST', url_prefix + 'images/e9aa60c60128/tag', params={ 'tag': 'tag', 'repo': 'repo', 'force': 0 }, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_tag_image_force(self): self.client.tag( fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME, force=True) fake_request.assert_called_with( 'POST', url_prefix + 'images/e9aa60c60128/tag', params={ 'tag': None, 'repo': 'repo', 'force': 1 }, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_get_image(self): self.client.get_image(fake_api.FAKE_IMAGE_ID) fake_request.assert_called_with( 'GET', url_prefix + 'images/e9aa60c60128/get', stream=True, timeout=DEFAULT_TIMEOUT_SECONDS ) def test_load_image(self): self.client.load_image('Byte Stream....') fake_request.assert_called_with( 'POST', url_prefix + 'images/load', data='Byte Stream....', timeout=DEFAULT_TIMEOUT_SECONDS ) docker-py-1.8.0/tests/unit/network_test.py0000644000175000017500000001414212702731056017271 0ustar kickkickimport json import six from .. import base from .api_test import DockerClientTest, url_prefix, response from docker.utils import create_ipam_config, create_ipam_pool try: from unittest import mock except ImportError: import mock class NetworkTest(DockerClientTest): @base.requires_api_version('1.21') def test_list_networks(self): networks = [ { "name": "none", "id": "8e4e55c6863ef424", "type": "null", "endpoints": [] }, { "name": "host", "id": "062b6d9ea7913fde", "type": "host", "endpoints": [] }, ] get = mock.Mock(return_value=response( status_code=200, content=json.dumps(networks).encode('utf-8'))) with mock.patch('docker.Client.get', get): self.assertEqual(self.client.networks(), networks) self.assertEqual(get.call_args[0][0], url_prefix + 'networks') filters = json.loads(get.call_args[1]['params']['filters']) self.assertFalse(filters) self.client.networks(names=['foo']) filters = json.loads(get.call_args[1]['params']['filters']) self.assertEqual(filters, {'name': ['foo']}) self.client.networks(ids=['123']) filters = json.loads(get.call_args[1]['params']['filters']) self.assertEqual(filters, {'id': ['123']}) @base.requires_api_version('1.21') def test_create_network(self): network_data = { "id": 'abc12345', "warning": "", } network_response = response(status_code=200, content=network_data) post = mock.Mock(return_value=network_response) with mock.patch('docker.Client.post', post): result = self.client.create_network('foo') self.assertEqual(result, network_data) self.assertEqual( post.call_args[0][0], url_prefix + 'networks/create') self.assertEqual( json.loads(post.call_args[1]['data']), {"Name": "foo"}) opts = { 'com.docker.network.bridge.enable_icc': False, 'com.docker.network.bridge.enable_ip_masquerade': False, } self.client.create_network('foo', 'bridge', opts) self.assertEqual( json.loads(post.call_args[1]['data']), {"Name": "foo", "Driver": "bridge", "Options": opts}) ipam_pool_config = create_ipam_pool(subnet="192.168.52.0/24", gateway="192.168.52.254") ipam_config = create_ipam_config(pool_configs=[ipam_pool_config]) self.client.create_network("bar", driver="bridge", ipam=ipam_config) self.assertEqual( json.loads(post.call_args[1]['data']), { "Name": "bar", "Driver": "bridge", "IPAM": { "Driver": "default", "Config": [{ "IPRange": None, "Gateway": "192.168.52.254", "Subnet": "192.168.52.0/24", "AuxiliaryAddresses": None, }] } }) @base.requires_api_version('1.21') def test_remove_network(self): network_id = 'abc12345' delete = mock.Mock(return_value=response(status_code=200)) with mock.patch('docker.Client.delete', delete): self.client.remove_network(network_id) args = delete.call_args self.assertEqual(args[0][0], url_prefix + 'networks/{0}'.format(network_id)) @base.requires_api_version('1.21') def test_inspect_network(self): network_id = 'abc12345' network_name = 'foo' network_data = { six.u('name'): network_name, six.u('id'): network_id, six.u('driver'): 'bridge', six.u('containers'): {}, } network_response = response(status_code=200, content=network_data) get = mock.Mock(return_value=network_response) with mock.patch('docker.Client.get', get): result = self.client.inspect_network(network_id) self.assertEqual(result, network_data) args = get.call_args self.assertEqual(args[0][0], url_prefix + 'networks/{0}'.format(network_id)) @base.requires_api_version('1.21') def test_connect_container_to_network(self): network_id = 'abc12345' container_id = 'def45678' post = mock.Mock(return_value=response(status_code=201)) with mock.patch('docker.Client.post', post): self.client.connect_container_to_network( {'Id': container_id}, network_id, aliases=['foo', 'bar'], links=[('baz', 'quux')] ) self.assertEqual( post.call_args[0][0], url_prefix + 'networks/{0}/connect'.format(network_id)) self.assertEqual( json.loads(post.call_args[1]['data']), { 'Container': container_id, 'EndpointConfig': { 'Aliases': ['foo', 'bar'], 'Links': ['baz:quux'], }, }) @base.requires_api_version('1.21') def test_disconnect_container_from_network(self): network_id = 'abc12345' container_id = 'def45678' post = mock.Mock(return_value=response(status_code=201)) with mock.patch('docker.Client.post', post): self.client.disconnect_container_from_network( {'Id': container_id}, network_id) self.assertEqual( post.call_args[0][0], url_prefix + 'networks/{0}/disconnect'.format(network_id)) self.assertEqual( json.loads(post.call_args[1]['data']), {'container': container_id}) docker-py-1.8.0/tests/unit/ssladapter_test.py0000644000175000017500000000455212702731056017746 0ustar kickkickfrom docker.ssladapter import ssladapter from docker.ssladapter.ssl_match_hostname import ( match_hostname, CertificateError ) try: from ssl import OP_NO_SSLv3, OP_NO_SSLv2, OP_NO_TLSv1 except ImportError: OP_NO_SSLv2 = 0x1000000 OP_NO_SSLv3 = 0x2000000 OP_NO_TLSv1 = 0x4000000 from .. import base class SSLAdapterTest(base.BaseTestCase): def test_only_uses_tls(self): ssl_context = ssladapter.urllib3.util.ssl_.create_urllib3_context() assert ssl_context.options & OP_NO_SSLv3 assert ssl_context.options & OP_NO_SSLv2 assert not ssl_context.options & OP_NO_TLSv1 class MatchHostnameTest(base.BaseTestCase): cert = { 'issuer': ( (('countryName', u'US'),), (('stateOrProvinceName', u'California'),), (('localityName', u'San Francisco'),), (('organizationName', u'Docker Inc'),), (('organizationalUnitName', u'Docker-Python'),), (('commonName', u'localhost'),), (('emailAddress', u'info@docker.com'),) ), 'notAfter': 'Mar 25 23:08:23 2030 GMT', 'notBefore': u'Mar 25 23:08:23 2016 GMT', 'serialNumber': u'BD5F894C839C548F', 'subject': ( (('countryName', u'US'),), (('stateOrProvinceName', u'California'),), (('localityName', u'San Francisco'),), (('organizationName', u'Docker Inc'),), (('organizationalUnitName', u'Docker-Python'),), (('commonName', u'localhost'),), (('emailAddress', u'info@docker.com'),) ), 'subjectAltName': ( ('DNS', u'localhost'), ('DNS', u'*.gensokyo.jp'), ('IP Address', u'127.0.0.1'), ), 'version': 3 } def test_match_ip_address_success(self): assert match_hostname(self.cert, '127.0.0.1') is None def test_match_localhost_success(self): assert match_hostname(self.cert, 'localhost') is None def test_match_dns_success(self): assert match_hostname(self.cert, 'touhou.gensokyo.jp') is None def test_match_ip_address_failure(self): self.assertRaises( CertificateError, match_hostname, self.cert, '192.168.0.25' ) def test_match_dns_failure(self): self.assertRaises( CertificateError, match_hostname, self.cert, 'foobar.co.uk' ) docker-py-1.8.0/tests/unit/utils_test.py0000644000175000017500000010372712702731056016750 0ustar kickkick# -*- coding: utf-8 -*- import base64 import json import os import os.path import shutil import sys import tarfile import tempfile import pytest import six from docker.client import Client from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import DockerException, InvalidVersion from docker.utils import ( parse_repository_tag, parse_host, convert_filters, kwargs_from_env, create_host_config, Ulimit, LogConfig, parse_bytes, parse_env_file, exclude_paths, convert_volume_binds, decode_json_header, tar, split_command, create_ipam_config, create_ipam_pool, parse_devices, ) from docker.utils.utils import create_endpoint_config from docker.utils.ports import build_port_bindings, split_port from .. import base from ..helpers import make_tree TEST_CERT_DIR = os.path.join( os.path.dirname(__file__), 'testdata/certs', ) class HostConfigTest(base.BaseTestCase): def test_create_host_config_no_options(self): config = create_host_config(version='1.19') self.assertFalse('NetworkMode' in config) def test_create_host_config_no_options_newer_api_version(self): config = create_host_config(version='1.20') self.assertEqual(config['NetworkMode'], 'default') def test_create_host_config_invalid_cpu_cfs_types(self): with pytest.raises(TypeError): create_host_config(version='1.20', cpu_quota='0') with pytest.raises(TypeError): create_host_config(version='1.20', cpu_period='0') with pytest.raises(TypeError): create_host_config(version='1.20', cpu_quota=23.11) with pytest.raises(TypeError): create_host_config(version='1.20', cpu_period=1999.0) def test_create_host_config_with_cpu_quota(self): config = create_host_config(version='1.20', cpu_quota=1999) self.assertEqual(config.get('CpuQuota'), 1999) def test_create_host_config_with_cpu_period(self): config = create_host_config(version='1.20', cpu_period=1999) self.assertEqual(config.get('CpuPeriod'), 1999) def test_create_host_config_with_shm_size(self): config = create_host_config(version='1.22', shm_size=67108864) self.assertEqual(config.get('ShmSize'), 67108864) def test_create_host_config_with_shm_size_in_mb(self): config = create_host_config(version='1.22', shm_size='64M') self.assertEqual(config.get('ShmSize'), 67108864) def test_create_host_config_with_oom_kill_disable(self): config = create_host_config(version='1.20', oom_kill_disable=True) self.assertEqual(config.get('OomKillDisable'), True) self.assertRaises( InvalidVersion, lambda: create_host_config(version='1.18.3', oom_kill_disable=True)) def test_create_host_config_with_oom_score_adj(self): config = create_host_config(version='1.22', oom_score_adj=100) self.assertEqual(config.get('OomScoreAdj'), 100) self.assertRaises( InvalidVersion, lambda: create_host_config(version='1.21', oom_score_adj=100)) self.assertRaises( TypeError, lambda: create_host_config(version='1.22', oom_score_adj='100')) def test_create_endpoint_config_with_aliases(self): config = create_endpoint_config(version='1.22', aliases=['foo', 'bar']) assert config == {'Aliases': ['foo', 'bar']} with pytest.raises(InvalidVersion): create_endpoint_config(version='1.21', aliases=['foo', 'bar']) class UlimitTest(base.BaseTestCase): def test_create_host_config_dict_ulimit(self): ulimit_dct = {'name': 'nofile', 'soft': 8096} config = create_host_config( ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION ) self.assertIn('Ulimits', config) self.assertEqual(len(config['Ulimits']), 1) ulimit_obj = config['Ulimits'][0] self.assertTrue(isinstance(ulimit_obj, Ulimit)) self.assertEqual(ulimit_obj.name, ulimit_dct['name']) self.assertEqual(ulimit_obj.soft, ulimit_dct['soft']) self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft) def test_create_host_config_dict_ulimit_capitals(self): ulimit_dct = {'Name': 'nofile', 'Soft': 8096, 'Hard': 8096 * 4} config = create_host_config( ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION ) self.assertIn('Ulimits', config) self.assertEqual(len(config['Ulimits']), 1) ulimit_obj = config['Ulimits'][0] self.assertTrue(isinstance(ulimit_obj, Ulimit)) self.assertEqual(ulimit_obj.name, ulimit_dct['Name']) self.assertEqual(ulimit_obj.soft, ulimit_dct['Soft']) self.assertEqual(ulimit_obj.hard, ulimit_dct['Hard']) self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft) def test_create_host_config_obj_ulimit(self): ulimit_dct = Ulimit(name='nofile', soft=8096) config = create_host_config( ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION ) self.assertIn('Ulimits', config) self.assertEqual(len(config['Ulimits']), 1) ulimit_obj = config['Ulimits'][0] self.assertTrue(isinstance(ulimit_obj, Ulimit)) self.assertEqual(ulimit_obj, ulimit_dct) def test_ulimit_invalid_type(self): self.assertRaises(ValueError, lambda: Ulimit(name=None)) self.assertRaises(ValueError, lambda: Ulimit(name='hello', soft='123')) self.assertRaises(ValueError, lambda: Ulimit(name='hello', hard='456')) class LogConfigTest(base.BaseTestCase): def test_create_host_config_dict_logconfig(self): dct = {'type': LogConfig.types.SYSLOG, 'config': {'key1': 'val1'}} config = create_host_config( version=DEFAULT_DOCKER_API_VERSION, log_config=dct ) self.assertIn('LogConfig', config) self.assertTrue(isinstance(config['LogConfig'], LogConfig)) self.assertEqual(dct['type'], config['LogConfig'].type) def test_create_host_config_obj_logconfig(self): obj = LogConfig(type=LogConfig.types.SYSLOG, config={'key1': 'val1'}) config = create_host_config( version=DEFAULT_DOCKER_API_VERSION, log_config=obj ) self.assertIn('LogConfig', config) self.assertTrue(isinstance(config['LogConfig'], LogConfig)) self.assertEqual(obj, config['LogConfig']) def test_logconfig_invalid_config_type(self): with pytest.raises(ValueError): LogConfig(type=LogConfig.types.JSON, config='helloworld') class KwargsFromEnvTest(base.BaseTestCase): def setUp(self): self.os_environ = os.environ.copy() def tearDown(self): os.environ = self.os_environ def test_kwargs_from_env_empty(self): os.environ.update(DOCKER_HOST='', DOCKER_CERT_PATH='') os.environ.pop('DOCKER_TLS_VERIFY', None) kwargs = kwargs_from_env() self.assertEqual(None, kwargs.get('base_url')) self.assertEqual(None, kwargs.get('tls')) def test_kwargs_from_env_tls(self): os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', DOCKER_CERT_PATH=TEST_CERT_DIR, DOCKER_TLS_VERIFY='1') kwargs = kwargs_from_env(assert_hostname=False) self.assertEqual('https://192.168.59.103:2376', kwargs['base_url']) self.assertTrue('ca.pem' in kwargs['tls'].ca_cert) self.assertTrue('cert.pem' in kwargs['tls'].cert[0]) self.assertTrue('key.pem' in kwargs['tls'].cert[1]) self.assertEqual(False, kwargs['tls'].assert_hostname) self.assertTrue(kwargs['tls'].verify) try: client = Client(**kwargs) self.assertEqual(kwargs['base_url'], client.base_url) self.assertEqual(kwargs['tls'].ca_cert, client.verify) self.assertEqual(kwargs['tls'].cert, client.cert) except TypeError as e: self.fail(e) def test_kwargs_from_env_tls_verify_false(self): os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', DOCKER_CERT_PATH=TEST_CERT_DIR, DOCKER_TLS_VERIFY='') kwargs = kwargs_from_env(assert_hostname=True) self.assertEqual('https://192.168.59.103:2376', kwargs['base_url']) self.assertTrue('ca.pem' in kwargs['tls'].ca_cert) self.assertTrue('cert.pem' in kwargs['tls'].cert[0]) self.assertTrue('key.pem' in kwargs['tls'].cert[1]) self.assertEqual(True, kwargs['tls'].assert_hostname) self.assertEqual(False, kwargs['tls'].verify) try: client = Client(**kwargs) self.assertEqual(kwargs['base_url'], client.base_url) self.assertEqual(kwargs['tls'].cert, client.cert) self.assertFalse(kwargs['tls'].verify) except TypeError as e: self.fail(e) def test_kwargs_from_env_tls_verify_false_no_cert(self): temp_dir = tempfile.mkdtemp() cert_dir = os.path.join(temp_dir, '.docker') shutil.copytree(TEST_CERT_DIR, cert_dir) os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', HOME=temp_dir, DOCKER_TLS_VERIFY='') os.environ.pop('DOCKER_CERT_PATH', None) kwargs = kwargs_from_env(assert_hostname=True) self.assertEqual('tcp://192.168.59.103:2376', kwargs['base_url']) def test_kwargs_from_env_no_cert_path(self): try: temp_dir = tempfile.mkdtemp() cert_dir = os.path.join(temp_dir, '.docker') shutil.copytree(TEST_CERT_DIR, cert_dir) os.environ.update(HOME=temp_dir, DOCKER_CERT_PATH='', DOCKER_TLS_VERIFY='1') kwargs = kwargs_from_env() self.assertTrue(kwargs['tls'].verify) self.assertIn(cert_dir, kwargs['tls'].ca_cert) self.assertIn(cert_dir, kwargs['tls'].cert[0]) self.assertIn(cert_dir, kwargs['tls'].cert[1]) finally: if temp_dir: shutil.rmtree(temp_dir) def test_kwargs_from_env_alternate_env(self): # Values in os.environ are entirely ignored if an alternate is # provided os.environ.update( DOCKER_HOST='tcp://192.168.59.103:2376', DOCKER_CERT_PATH=TEST_CERT_DIR, DOCKER_TLS_VERIFY='' ) kwargs = kwargs_from_env(environment={ 'DOCKER_HOST': 'http://docker.gensokyo.jp:2581', }) assert 'http://docker.gensokyo.jp:2581' == kwargs['base_url'] assert 'tls' not in kwargs class ConverVolumeBindsTest(base.BaseTestCase): def test_convert_volume_binds_empty(self): self.assertEqual(convert_volume_binds({}), []) self.assertEqual(convert_volume_binds([]), []) def test_convert_volume_binds_list(self): data = ['/a:/a:ro', '/b:/c:z'] self.assertEqual(convert_volume_binds(data), data) def test_convert_volume_binds_complete(self): data = { '/mnt/vol1': { 'bind': '/data', 'mode': 'ro' } } self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:ro']) def test_convert_volume_binds_compact(self): data = { '/mnt/vol1': '/data' } self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:rw']) def test_convert_volume_binds_no_mode(self): data = { '/mnt/vol1': { 'bind': '/data' } } self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:rw']) def test_convert_volume_binds_unicode_bytes_input(self): if six.PY2: expected = [unicode('/mnt/지연:/unicode/박:rw', 'utf-8')] data = { '/mnt/지연': { 'bind': '/unicode/박', 'mode': 'rw' } } self.assertEqual( convert_volume_binds(data), expected ) else: expected = ['/mnt/지연:/unicode/박:rw'] data = { bytes('/mnt/지연', 'utf-8'): { 'bind': bytes('/unicode/박', 'utf-8'), 'mode': 'rw' } } self.assertEqual( convert_volume_binds(data), expected ) def test_convert_volume_binds_unicode_unicode_input(self): if six.PY2: expected = [unicode('/mnt/지연:/unicode/박:rw', 'utf-8')] data = { unicode('/mnt/지연', 'utf-8'): { 'bind': unicode('/unicode/박', 'utf-8'), 'mode': 'rw' } } self.assertEqual( convert_volume_binds(data), expected ) else: expected = ['/mnt/지연:/unicode/박:rw'] data = { '/mnt/지연': { 'bind': '/unicode/박', 'mode': 'rw' } } self.assertEqual( convert_volume_binds(data), expected ) class ParseEnvFileTest(base.BaseTestCase): def generate_tempfile(self, file_content=None): """ Generates a temporary file for tests with the content of 'file_content' and returns the filename. Don't forget to unlink the file with os.unlink() after. """ local_tempfile = tempfile.NamedTemporaryFile(delete=False) local_tempfile.write(file_content.encode('UTF-8')) local_tempfile.close() return local_tempfile.name def test_parse_env_file_proper(self): env_file = self.generate_tempfile( file_content='USER=jdoe\nPASS=secret') get_parse_env_file = parse_env_file(env_file) self.assertEqual(get_parse_env_file, {'USER': 'jdoe', 'PASS': 'secret'}) os.unlink(env_file) def test_parse_env_file_commented_line(self): env_file = self.generate_tempfile( file_content='USER=jdoe\n#PASS=secret') get_parse_env_file = parse_env_file((env_file)) self.assertEqual(get_parse_env_file, {'USER': 'jdoe'}) os.unlink(env_file) def test_parse_env_file_invalid_line(self): env_file = self.generate_tempfile( file_content='USER jdoe') self.assertRaises( DockerException, parse_env_file, env_file) os.unlink(env_file) class ParseHostTest(base.BaseTestCase): def test_parse_host(self): invalid_hosts = [ '0.0.0.0', 'tcp://', 'udp://127.0.0.1', 'udp://127.0.0.1:2375', ] valid_hosts = { '0.0.0.1:5555': 'http://0.0.0.1:5555', ':6666': 'http://127.0.0.1:6666', 'tcp://:7777': 'http://127.0.0.1:7777', 'http://:7777': 'http://127.0.0.1:7777', 'https://kokia.jp:2375': 'https://kokia.jp:2375', 'unix:///var/run/docker.sock': 'http+unix:///var/run/docker.sock', 'unix://': 'http+unix://var/run/docker.sock', 'somehost.net:80/service/swarm': ( 'http://somehost.net:80/service/swarm' ), } for host in invalid_hosts: with pytest.raises(DockerException): parse_host(host, None) for host, expected in valid_hosts.items(): self.assertEqual(parse_host(host, None), expected, msg=host) def test_parse_host_empty_value(self): unix_socket = 'http+unix://var/run/docker.sock' tcp_port = 'http://127.0.0.1:2375' for val in [None, '']: for platform in ['darwin', 'linux2', None]: assert parse_host(val, platform) == unix_socket assert parse_host(val, 'win32') == tcp_port def test_parse_host_tls(self): host_value = 'myhost.docker.net:3348' expected_result = 'https://myhost.docker.net:3348' assert parse_host(host_value, tls=True) == expected_result def test_parse_host_tls_tcp_proto(self): host_value = 'tcp://myhost.docker.net:3348' expected_result = 'https://myhost.docker.net:3348' assert parse_host(host_value, tls=True) == expected_result class ParseRepositoryTagTest(base.BaseTestCase): sha = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' def test_index_image_no_tag(self): self.assertEqual( parse_repository_tag("root"), ("root", None) ) def test_index_image_tag(self): self.assertEqual( parse_repository_tag("root:tag"), ("root", "tag") ) def test_index_user_image_no_tag(self): self.assertEqual( parse_repository_tag("user/repo"), ("user/repo", None) ) def test_index_user_image_tag(self): self.assertEqual( parse_repository_tag("user/repo:tag"), ("user/repo", "tag") ) def test_private_reg_image_no_tag(self): self.assertEqual( parse_repository_tag("url:5000/repo"), ("url:5000/repo", None) ) def test_private_reg_image_tag(self): self.assertEqual( parse_repository_tag("url:5000/repo:tag"), ("url:5000/repo", "tag") ) def test_index_image_sha(self): self.assertEqual( parse_repository_tag("root@sha256:{0}".format(self.sha)), ("root", "sha256:{0}".format(self.sha)) ) def test_private_reg_image_sha(self): self.assertEqual( parse_repository_tag("url:5000/repo@sha256:{0}".format(self.sha)), ("url:5000/repo", "sha256:{0}".format(self.sha)) ) class ParseDeviceTest(base.BaseTestCase): def test_dict(self): devices = parse_devices([{ 'PathOnHost': '/dev/sda1', 'PathInContainer': '/dev/mnt1', 'CgroupPermissions': 'r' }]) self.assertEqual(devices[0], { 'PathOnHost': '/dev/sda1', 'PathInContainer': '/dev/mnt1', 'CgroupPermissions': 'r' }) def test_partial_string_definition(self): devices = parse_devices(['/dev/sda1']) self.assertEqual(devices[0], { 'PathOnHost': '/dev/sda1', 'PathInContainer': '/dev/sda1', 'CgroupPermissions': 'rwm' }) def test_permissionless_string_definition(self): devices = parse_devices(['/dev/sda1:/dev/mnt1']) self.assertEqual(devices[0], { 'PathOnHost': '/dev/sda1', 'PathInContainer': '/dev/mnt1', 'CgroupPermissions': 'rwm' }) def test_full_string_definition(self): devices = parse_devices(['/dev/sda1:/dev/mnt1:r']) self.assertEqual(devices[0], { 'PathOnHost': '/dev/sda1', 'PathInContainer': '/dev/mnt1', 'CgroupPermissions': 'r' }) def test_hybrid_list(self): devices = parse_devices([ '/dev/sda1:/dev/mnt1:rw', { 'PathOnHost': '/dev/sda2', 'PathInContainer': '/dev/mnt2', 'CgroupPermissions': 'r' } ]) self.assertEqual(devices[0], { 'PathOnHost': '/dev/sda1', 'PathInContainer': '/dev/mnt1', 'CgroupPermissions': 'rw' }) self.assertEqual(devices[1], { 'PathOnHost': '/dev/sda2', 'PathInContainer': '/dev/mnt2', 'CgroupPermissions': 'r' }) class ParseBytesTest(base.BaseTestCase): def test_parse_bytes_valid(self): self.assertEqual(parse_bytes("512MB"), 536870912) self.assertEqual(parse_bytes("512M"), 536870912) self.assertEqual(parse_bytes("512m"), 536870912) def test_parse_bytes_invalid(self): self.assertRaises(DockerException, parse_bytes, "512MK") self.assertRaises(DockerException, parse_bytes, "512L") self.assertRaises(DockerException, parse_bytes, "127.0.0.1K") def test_parse_bytes_float(self): self.assertRaises(DockerException, parse_bytes, "1.5k") def test_parse_bytes_maxint(self): self.assertEqual( parse_bytes("{0}k".format(sys.maxsize)), sys.maxsize * 1024 ) class UtilsTest(base.BaseTestCase): longMessage = True def test_convert_filters(self): tests = [ ({'dangling': True}, '{"dangling": ["true"]}'), ({'dangling': "true"}, '{"dangling": ["true"]}'), ({'exited': 0}, '{"exited": [0]}'), ({'exited': [0, 1]}, '{"exited": [0, 1]}'), ] for filters, expected in tests: self.assertEqual(convert_filters(filters), expected) def test_decode_json_header(self): obj = {'a': 'b', 'c': 1} data = None if six.PY3: data = base64.urlsafe_b64encode(bytes(json.dumps(obj), 'utf-8')) else: data = base64.urlsafe_b64encode(json.dumps(obj)) decoded_data = decode_json_header(data) self.assertEqual(obj, decoded_data) def test_create_ipam_config(self): ipam_pool = create_ipam_pool(subnet='192.168.52.0/24', gateway='192.168.52.254') ipam_config = create_ipam_config(pool_configs=[ipam_pool]) self.assertEqual(ipam_config, { 'Driver': 'default', 'Config': [{ 'Subnet': '192.168.52.0/24', 'Gateway': '192.168.52.254', 'AuxiliaryAddresses': None, 'IPRange': None, }] }) class SplitCommandTest(base.BaseTestCase): def test_split_command_with_unicode(self): if six.PY2: self.assertEqual( split_command(unicode('echo μμ', 'utf-8')), ['echo', 'μμ'] ) else: self.assertEqual(split_command('echo μμ'), ['echo', 'μμ']) @pytest.mark.skipif(six.PY3, reason="shlex doesn't support bytes in py3") def test_split_command_with_bytes(self): self.assertEqual(split_command('echo μμ'), ['echo', 'μμ']) class PortsTest(base.BaseTestCase): def test_split_port_with_host_ip(self): internal_port, external_port = split_port("127.0.0.1:1000:2000") self.assertEqual(internal_port, ["2000"]) self.assertEqual(external_port, [("127.0.0.1", "1000")]) def test_split_port_with_protocol(self): internal_port, external_port = split_port("127.0.0.1:1000:2000/udp") self.assertEqual(internal_port, ["2000/udp"]) self.assertEqual(external_port, [("127.0.0.1", "1000")]) def test_split_port_with_host_ip_no_port(self): internal_port, external_port = split_port("127.0.0.1::2000") self.assertEqual(internal_port, ["2000"]) self.assertEqual(external_port, [("127.0.0.1", None)]) def test_split_port_range_with_host_ip_no_port(self): internal_port, external_port = split_port("127.0.0.1::2000-2001") self.assertEqual(internal_port, ["2000", "2001"]) self.assertEqual(external_port, [("127.0.0.1", None), ("127.0.0.1", None)]) def test_split_port_with_host_port(self): internal_port, external_port = split_port("1000:2000") self.assertEqual(internal_port, ["2000"]) self.assertEqual(external_port, ["1000"]) def test_split_port_range_with_host_port(self): internal_port, external_port = split_port("1000-1001:2000-2001") self.assertEqual(internal_port, ["2000", "2001"]) self.assertEqual(external_port, ["1000", "1001"]) def test_split_port_no_host_port(self): internal_port, external_port = split_port("2000") self.assertEqual(internal_port, ["2000"]) self.assertEqual(external_port, None) def test_split_port_range_no_host_port(self): internal_port, external_port = split_port("2000-2001") self.assertEqual(internal_port, ["2000", "2001"]) self.assertEqual(external_port, None) def test_split_port_range_with_protocol(self): internal_port, external_port = split_port( "127.0.0.1:1000-1001:2000-2001/udp") self.assertEqual(internal_port, ["2000/udp", "2001/udp"]) self.assertEqual(external_port, [("127.0.0.1", "1000"), ("127.0.0.1", "1001")]) def test_split_port_invalid(self): self.assertRaises(ValueError, lambda: split_port("0.0.0.0:1000:2000:tcp")) def test_non_matching_length_port_ranges(self): self.assertRaises( ValueError, lambda: split_port("0.0.0.0:1000-1010:2000-2002/tcp") ) def test_port_and_range_invalid(self): self.assertRaises(ValueError, lambda: split_port("0.0.0.0:1000:2000-2002/tcp")) def test_port_only_with_colon(self): self.assertRaises(ValueError, lambda: split_port(":80")) def test_host_only_with_colon(self): self.assertRaises(ValueError, lambda: split_port("localhost:")) def test_build_port_bindings_with_one_port(self): port_bindings = build_port_bindings(["127.0.0.1:1000:1000"]) self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) def test_build_port_bindings_with_matching_internal_ports(self): port_bindings = build_port_bindings( ["127.0.0.1:1000:1000", "127.0.0.1:2000:1000"]) self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000"), ("127.0.0.1", "2000")]) def test_build_port_bindings_with_nonmatching_internal_ports(self): port_bindings = build_port_bindings( ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) def test_build_port_bindings_with_port_range(self): port_bindings = build_port_bindings(["127.0.0.1:1000-1001:1000-1001"]) self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) self.assertEqual(port_bindings["1001"], [("127.0.0.1", "1001")]) def test_build_port_bindings_with_matching_internal_port_ranges(self): port_bindings = build_port_bindings( ["127.0.0.1:1000-1001:1000-1001", "127.0.0.1:2000-2001:1000-1001"]) self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000"), ("127.0.0.1", "2000")]) self.assertEqual(port_bindings["1001"], [("127.0.0.1", "1001"), ("127.0.0.1", "2001")]) def test_build_port_bindings_with_nonmatching_internal_port_ranges(self): port_bindings = build_port_bindings( ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) class ExcludePathsTest(base.BaseTestCase): dirs = [ 'foo', 'foo/bar', 'bar', ] files = [ 'Dockerfile', 'Dockerfile.alt', '.dockerignore', 'a.py', 'a.go', 'b.py', 'cde.py', 'foo/a.py', 'foo/b.py', 'foo/bar/a.py', 'bar/a.py', 'foo/Dockerfile3', ] all_paths = set(dirs + files) def setUp(self): self.base = make_tree(self.dirs, self.files) def tearDown(self): shutil.rmtree(self.base) def exclude(self, patterns, dockerfile=None): return set(exclude_paths(self.base, patterns, dockerfile=dockerfile)) def test_no_excludes(self): assert self.exclude(['']) == self.all_paths def test_no_dupes(self): paths = exclude_paths(self.base, ['!a.py']) assert sorted(paths) == sorted(set(paths)) def test_wildcard_exclude(self): assert self.exclude(['*']) == set(['Dockerfile', '.dockerignore']) def test_exclude_dockerfile_dockerignore(self): """ Even if the .dockerignore file explicitly says to exclude Dockerfile and/or .dockerignore, don't exclude them from the actual tar file. """ assert self.exclude(['Dockerfile', '.dockerignore']) == self.all_paths def test_exclude_custom_dockerfile(self): """ If we're using a custom Dockerfile, make sure that's not excluded. """ assert self.exclude(['*'], dockerfile='Dockerfile.alt') == \ set(['Dockerfile.alt', '.dockerignore']) assert self.exclude(['*'], dockerfile='foo/Dockerfile3') == \ set(['foo/Dockerfile3', '.dockerignore']) def test_exclude_dockerfile_child(self): includes = self.exclude(['foo/'], dockerfile='foo/Dockerfile3') assert 'foo/Dockerfile3' in includes assert 'foo/a.py' not in includes def test_single_filename(self): assert self.exclude(['a.py']) == self.all_paths - set(['a.py']) # As odd as it sounds, a filename pattern with a trailing slash on the # end *will* result in that file being excluded. def test_single_filename_trailing_slash(self): assert self.exclude(['a.py/']) == self.all_paths - set(['a.py']) def test_wildcard_filename_start(self): assert self.exclude(['*.py']) == self.all_paths - set([ 'a.py', 'b.py', 'cde.py', ]) def test_wildcard_with_exception(self): assert self.exclude(['*.py', '!b.py']) == self.all_paths - set([ 'a.py', 'cde.py', ]) def test_wildcard_with_wildcard_exception(self): assert self.exclude(['*.*', '!*.go']) == self.all_paths - set([ 'a.py', 'b.py', 'cde.py', 'Dockerfile.alt', ]) def test_wildcard_filename_end(self): assert self.exclude(['a.*']) == self.all_paths - set(['a.py', 'a.go']) def test_question_mark(self): assert self.exclude(['?.py']) == self.all_paths - set(['a.py', 'b.py']) def test_single_subdir_single_filename(self): assert self.exclude(['foo/a.py']) == self.all_paths - set(['foo/a.py']) def test_single_subdir_wildcard_filename(self): assert self.exclude(['foo/*.py']) == self.all_paths - set([ 'foo/a.py', 'foo/b.py', ]) def test_wildcard_subdir_single_filename(self): assert self.exclude(['*/a.py']) == self.all_paths - set([ 'foo/a.py', 'bar/a.py', ]) def test_wildcard_subdir_wildcard_filename(self): assert self.exclude(['*/*.py']) == self.all_paths - set([ 'foo/a.py', 'foo/b.py', 'bar/a.py', ]) def test_directory(self): assert self.exclude(['foo']) == self.all_paths - set([ 'foo', 'foo/a.py', 'foo/b.py', 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' ]) def test_directory_with_trailing_slash(self): assert self.exclude(['foo']) == self.all_paths - set([ 'foo', 'foo/a.py', 'foo/b.py', 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' ]) def test_directory_with_single_exception(self): assert self.exclude(['foo', '!foo/bar/a.py']) == self.all_paths - set([ 'foo/a.py', 'foo/b.py', 'foo', 'foo/bar', 'foo/Dockerfile3' ]) def test_directory_with_subdir_exception(self): assert self.exclude(['foo', '!foo/bar']) == self.all_paths - set([ 'foo/a.py', 'foo/b.py', 'foo', 'foo/Dockerfile3' ]) def test_directory_with_wildcard_exception(self): assert self.exclude(['foo', '!foo/*.py']) == self.all_paths - set([ 'foo/bar', 'foo/bar/a.py', 'foo', 'foo/Dockerfile3' ]) def test_subdirectory(self): assert self.exclude(['foo/bar']) == self.all_paths - set([ 'foo/bar', 'foo/bar/a.py', ]) class TarTest(base.Cleanup, base.BaseTestCase): def test_tar_with_excludes(self): dirs = [ 'foo', 'foo/bar', 'bar', ] files = [ 'Dockerfile', 'Dockerfile.alt', '.dockerignore', 'a.py', 'a.go', 'b.py', 'cde.py', 'foo/a.py', 'foo/b.py', 'foo/bar/a.py', 'bar/a.py', ] exclude = [ '*.py', '!b.py', '!a.go', 'foo', 'Dockerfile*', '.dockerignore', ] expected_names = set([ 'Dockerfile', '.dockerignore', 'a.go', 'b.py', 'bar', 'bar/a.py', ]) base = make_tree(dirs, files) self.addCleanup(shutil.rmtree, base) with tar(base, exclude=exclude) as archive: tar_data = tarfile.open(fileobj=archive) assert sorted(tar_data.getnames()) == sorted(expected_names) def test_tar_with_empty_directory(self): base = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, base) for d in ['foo', 'bar']: os.makedirs(os.path.join(base, d)) with tar(base) as archive: tar_data = tarfile.open(fileobj=archive) self.assertEqual(sorted(tar_data.getnames()), ['bar', 'foo']) def test_tar_with_file_symlinks(self): base = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, base) with open(os.path.join(base, 'foo'), 'w') as f: f.write("content") os.makedirs(os.path.join(base, 'bar')) os.symlink('../foo', os.path.join(base, 'bar/foo')) with tar(base) as archive: tar_data = tarfile.open(fileobj=archive) self.assertEqual( sorted(tar_data.getnames()), ['bar', 'bar/foo', 'foo'] ) def test_tar_with_directory_symlinks(self): base = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, base) for d in ['foo', 'bar']: os.makedirs(os.path.join(base, d)) os.symlink('../foo', os.path.join(base, 'bar/foo')) with tar(base) as archive: tar_data = tarfile.open(fileobj=archive) self.assertEqual( sorted(tar_data.getnames()), ['bar', 'bar/foo', 'foo'] ) docker-py-1.8.0/tests/unit/volume_test.py0000644000175000017500000000653212702731056017113 0ustar kickkickimport json import pytest from .. import base from .api_test import DockerClientTest, url_prefix, fake_request class VolumeTest(DockerClientTest): @base.requires_api_version('1.21') def test_list_volumes(self): volumes = self.client.volumes() self.assertIn('Volumes', volumes) self.assertEqual(len(volumes['Volumes']), 2) args = fake_request.call_args self.assertEqual(args[0][0], 'GET') self.assertEqual(args[0][1], url_prefix + 'volumes') @base.requires_api_version('1.21') def test_list_volumes_and_filters(self): volumes = self.client.volumes(filters={'dangling': True}) assert 'Volumes' in volumes assert len(volumes['Volumes']) == 2 args = fake_request.call_args assert args[0][0] == 'GET' assert args[0][1] == url_prefix + 'volumes' assert args[1] == {'params': {'filters': '{"dangling": ["true"]}'}, 'timeout': 60} @base.requires_api_version('1.21') def test_create_volume(self): name = 'perfectcherryblossom' result = self.client.create_volume(name) self.assertIn('Name', result) self.assertEqual(result['Name'], name) self.assertIn('Driver', result) self.assertEqual(result['Driver'], 'local') args = fake_request.call_args self.assertEqual(args[0][0], 'POST') self.assertEqual(args[0][1], url_prefix + 'volumes/create') self.assertEqual(json.loads(args[1]['data']), {'Name': name}) @base.requires_api_version('1.21') def test_create_volume_with_driver(self): name = 'perfectcherryblossom' driver_name = 'sshfs' self.client.create_volume(name, driver=driver_name) args = fake_request.call_args self.assertEqual(args[0][0], 'POST') self.assertEqual(args[0][1], url_prefix + 'volumes/create') data = json.loads(args[1]['data']) self.assertIn('Driver', data) self.assertEqual(data['Driver'], driver_name) @base.requires_api_version('1.21') def test_create_volume_invalid_opts_type(self): with pytest.raises(TypeError): self.client.create_volume( 'perfectcherryblossom', driver_opts='hello=world' ) with pytest.raises(TypeError): self.client.create_volume( 'perfectcherryblossom', driver_opts=['hello=world'] ) with pytest.raises(TypeError): self.client.create_volume( 'perfectcherryblossom', driver_opts='' ) @base.requires_api_version('1.21') def test_inspect_volume(self): name = 'perfectcherryblossom' result = self.client.inspect_volume(name) self.assertIn('Name', result) self.assertEqual(result['Name'], name) self.assertIn('Driver', result) self.assertEqual(result['Driver'], 'local') args = fake_request.call_args self.assertEqual(args[0][0], 'GET') self.assertEqual(args[0][1], '{0}volumes/{1}'.format(url_prefix, name)) @base.requires_api_version('1.21') def test_remove_volume(self): name = 'perfectcherryblossom' self.client.remove_volume(name) args = fake_request.call_args self.assertEqual(args[0][0], 'DELETE') self.assertEqual(args[0][1], '{0}volumes/{1}'.format(url_prefix, name)) docker-py-1.8.0/tests/base.py0000644000175000017500000000254112702731056014474 0ustar kickkickimport sys import unittest import pytest import six import docker class BaseTestCase(unittest.TestCase): def assertIn(self, object, collection): if six.PY2 and sys.version_info[1] <= 6: return self.assertTrue(object in collection) return super(BaseTestCase, self).assertIn(object, collection) def requires_api_version(version): return pytest.mark.skipif( docker.utils.version_lt( docker.constants.DEFAULT_DOCKER_API_VERSION, version ), reason="API version is too low (< {0})".format(version) ) class Cleanup(object): if sys.version_info < (2, 7): # Provide a basic implementation of addCleanup for Python < 2.7 def __init__(self, *args, **kwargs): super(Cleanup, self).__init__(*args, **kwargs) self._cleanups = [] def tearDown(self): super(Cleanup, self).tearDown() ok = True while self._cleanups: fn, args, kwargs = self._cleanups.pop(-1) try: fn(*args, **kwargs) except KeyboardInterrupt: raise except: ok = False if not ok: raise def addCleanup(self, function, *args, **kwargs): self._cleanups.append((function, args, kwargs)) docker-py-1.8.0/tests/helpers.py0000644000175000017500000001177012702731056015230 0ustar kickkickimport errno import os import os.path import select import shutil import struct import tarfile import tempfile import unittest import docker import six BUSYBOX = 'busybox:buildroot-2014.02' EXEC_DRIVER = [] def make_tree(dirs, files): base = tempfile.mkdtemp() for path in dirs: os.makedirs(os.path.join(base, path)) for path in files: with open(os.path.join(base, path), 'w') as f: f.write("content") return base def simple_tar(path): f = tempfile.NamedTemporaryFile() t = tarfile.open(mode='w', fileobj=f) abs_path = os.path.abspath(path) t.add(abs_path, arcname=os.path.basename(path), recursive=False) t.close() f.seek(0) return f def untar_file(tardata, filename): with tarfile.open(mode='r', fileobj=tardata) as t: f = t.extractfile(filename) result = f.read() f.close() return result def exec_driver_is_native(): global EXEC_DRIVER if not EXEC_DRIVER: c = docker_client() EXEC_DRIVER = c.info()['ExecutionDriver'] c.close() return EXEC_DRIVER.startswith('native') def docker_client(**kwargs): return docker.Client(**docker_client_kwargs(**kwargs)) def docker_client_kwargs(**kwargs): client_kwargs = docker.utils.kwargs_from_env(assert_hostname=False) client_kwargs.update(kwargs) return client_kwargs def read_socket(socket, n=4096): """ Code stolen from dockerpty to read the socket """ recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK) # wait for data to become available select.select([socket], [], []) try: if hasattr(socket, 'recv'): return socket.recv(n) return os.read(socket.fileno(), n) except EnvironmentError as e: if e.errno not in recoverable_errors: raise def next_packet_size(socket): """ Code stolen from dockerpty to get the next packet size """ data = six.binary_type() while len(data) < 8: next_data = read_socket(socket, 8 - len(data)) if not next_data: return 0 data = data + next_data if data is None: return 0 if len(data) == 8: _, actual = struct.unpack('>BxxxL', data) return actual def read_data(socket, packet_size): data = six.binary_type() while len(data) < packet_size: next_data = read_socket(socket, packet_size - len(data)) if not next_data: assert False, "Failed trying to read in the dataz" data += next_data return data class BaseTestCase(unittest.TestCase): tmp_imgs = [] tmp_containers = [] tmp_folders = [] tmp_volumes = [] def setUp(self): if six.PY2: self.assertRegex = self.assertRegexpMatches self.assertCountEqual = self.assertItemsEqual self.client = docker_client(timeout=60) self.tmp_imgs = [] self.tmp_containers = [] self.tmp_folders = [] self.tmp_volumes = [] self.tmp_networks = [] def tearDown(self): for img in self.tmp_imgs: try: self.client.remove_image(img) except docker.errors.APIError: pass for container in self.tmp_containers: try: self.client.stop(container, timeout=1) self.client.remove_container(container) except docker.errors.APIError: pass for network in self.tmp_networks: try: self.client.remove_network(network) except docker.errors.APIError: pass for folder in self.tmp_folders: shutil.rmtree(folder) for volume in self.tmp_volumes: try: self.client.remove_volume(volume) except docker.errors.APIError: pass self.client.close() def run_container(self, *args, **kwargs): container = self.client.create_container(*args, **kwargs) self.tmp_containers.append(container) self.client.start(container) exitcode = self.client.wait(container) if exitcode != 0: output = self.client.logs(container) raise Exception( "Container exited with code {}:\n{}" .format(exitcode, output)) return container def create_and_start(self, image='busybox', command='top', **kwargs): container = self.client.create_container( image=image, command=command, **kwargs) self.tmp_containers.append(container) self.client.start(container) return container def execute(self, container, cmd, exit_code=0, **kwargs): exc = self.client.exec_create(container, cmd, **kwargs) output = self.client.exec_start(exc) actual_exit_code = self.client.exec_inspect(exc)['ExitCode'] msg = "Expected `{}` to exit with code {} but returned {}:\n{}".format( " ".join(cmd), exit_code, actual_exit_code, output) assert actual_exit_code == exit_code, msg docker-py-1.8.0/tests/__init__.py0000644000175000017500000000000012702731056015305 0ustar kickkickdocker-py-1.8.0/tests/Dockerfile-dind-certs0000644000175000017500000000235112702731056017233 0ustar kickkickFROM python:2.7 RUN mkdir /tmp/certs VOLUME /certs WORKDIR /tmp/certs RUN openssl genrsa -aes256 -passout pass:foobar -out ca-key.pem 4096 RUN echo "[req]\nprompt=no\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\ncountryName=AU" > /tmp/config RUN openssl req -new -x509 -passin pass:foobar -config /tmp/config -days 365 -key ca-key.pem -sha256 -out ca.pem RUN openssl genrsa -out server-key.pem -passout pass:foobar 4096 RUN openssl req -subj "/CN=docker" -sha256 -new -key server-key.pem -out server.csr RUN echo subjectAltName = DNS:docker,DNS:localhost > extfile.cnf RUN openssl x509 -req -days 365 -passin pass:foobar -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf RUN openssl genrsa -out key.pem 4096 RUN openssl req -passin pass:foobar -subj '/CN=client' -new -key key.pem -out client.csr RUN echo extendedKeyUsage = clientAuth > extfile.cnf RUN openssl x509 -req -passin pass:foobar -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf RUN chmod -v 0400 ca-key.pem key.pem server-key.pem RUN chmod -v 0444 ca.pem server-cert.pem cert.pem CMD cp -R /tmp/certs/* /certs && while true; do sleep 1; done docker-py-1.8.0/docker_py.egg-info/0000775000175000017500000000000012702736203015516 5ustar kickkickdocker-py-1.8.0/docker_py.egg-info/PKG-INFO0000664000175000017500000000135212702733155016617 0ustar kickkickMetadata-Version: 1.1 Name: docker-py Version: 1.8.0 Summary: Python client for Docker. Home-page: https://github.com/docker/docker-py/ Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Other Environment Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Utilities Classifier: License :: OSI Approved :: Apache Software License docker-py-1.8.0/docker_py.egg-info/not-zip-safe0000664000175000017500000000000112702733155017747 0ustar kickkick docker-py-1.8.0/docker_py.egg-info/top_level.txt0000664000175000017500000000000712702733155020250 0ustar kickkickdocker docker-py-1.8.0/docker_py.egg-info/dependency_links.txt0000664000175000017500000000000112702733155021567 0ustar kickkick docker-py-1.8.0/docker_py.egg-info/SOURCES.txt0000664000175000017500000000332512702733155017410 0ustar kickkickLICENSE MANIFEST.in README.md requirements.txt setup.cfg setup.py test-requirements.txt docker/__init__.py docker/client.py docker/constants.py docker/errors.py docker/tls.py docker/version.py docker/api/__init__.py docker/api/build.py docker/api/container.py docker/api/daemon.py docker/api/exec_api.py docker/api/image.py docker/api/network.py docker/api/volume.py docker/auth/__init__.py docker/auth/auth.py docker/ssladapter/__init__.py docker/ssladapter/ssl_match_hostname.py docker/ssladapter/ssladapter.py docker/unixconn/__init__.py docker/unixconn/unixconn.py docker/utils/__init__.py docker/utils/decorators.py docker/utils/types.py docker/utils/utils.py docker/utils/ports/__init__.py docker/utils/ports/ports.py docker_py.egg-info/PKG-INFO docker_py.egg-info/SOURCES.txt docker_py.egg-info/dependency_links.txt docker_py.egg-info/not-zip-safe docker_py.egg-info/requires.txt docker_py.egg-info/top_level.txt tests/__init__.py tests/base.py tests/helpers.py tests/integration/__init__.py tests/integration/api_test.py tests/integration/build_test.py tests/integration/conftest.py tests/integration/container_test.py tests/integration/exec_test.py tests/integration/image_test.py tests/integration/network_test.py tests/integration/regression_test.py tests/integration/volume_test.py tests/unit/__init__.py tests/unit/api_test.py tests/unit/auth_test.py tests/unit/build_test.py tests/unit/client_test.py tests/unit/container_test.py tests/unit/exec_test.py tests/unit/fake_api.py tests/unit/fake_stat.py tests/unit/image_test.py tests/unit/network_test.py tests/unit/ssladapter_test.py tests/unit/utils_test.py tests/unit/volume_test.py tests/unit/testdata/certs/ca.pem tests/unit/testdata/certs/cert.pem tests/unit/testdata/certs/key.pemdocker-py-1.8.0/docker_py.egg-info/requires.txt0000664000175000017500000000015212702736203020114 0ustar kickkickrequests >= 2.5.2 six >= 1.4.0 websocket-client >= 0.32.0 [:python_version < "3"] py2-ipaddress >= 3.4.1 docker-py-1.8.0/.coveragerc0000644000175000017500000000016612702731056014170 0ustar kickkick[run] branch = True source = docker [report] exclude_lines = if __name__ == .__main__.: [html] directory = html docker-py-1.8.0/.dockerignore0000644000175000017500000000017412702731056014522 0ustar kickkick.git/ build dist *.egg-info *.egg/ *.pyc *.swp .tox .coverage html/* __pycache__ # Compiled Documentation site/ Makefile docker-py-1.8.0/.editorconfig0000644000175000017500000000026012702731056014517 0ustar kickkickroot = true [*] indent_style = space indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true max_line_length = 80 [*.md] trim_trailing_whitespace = false docker-py-1.8.0/.gitignore0000644000175000017500000000017612702731056014040 0ustar kickkickbuild dist *.egg-info *.egg/ *.pyc *.swp .tox .coverage html/* # Compiled Documentation site/ README.rst env/ venv/ .idea/ docker-py-1.8.0/LICENSE0000644000175000017500000002613612702731056013061 0ustar kickkick Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. docker-py-1.8.0/MANIFEST.in0000644000175000017500000000026112702731056013601 0ustar kickkickinclude test-requirements.txt include requirements.txt include README.md include README.rst include LICENSE recursive-include tests *.py recursive-include tests/unit/testdata * docker-py-1.8.0/README.md0000644000175000017500000000147112702731056013326 0ustar kickkickdocker-py ========= [![Build Status](https://travis-ci.org/docker/docker-py.png)](https://travis-ci.org/docker/docker-py) A Python library for the Docker Remote API. It does everything the `docker` command does, but from within Python – run containers, manage them, pull/push images, etc. Installation ------------ The latest stable version is always available on PyPi. pip install docker-py Documentation ------------ [![Documentation Status](https://readthedocs.org/projects/docker-py/badge/?version=latest)](https://readthedocs.org/projects/docker-py/?badge=latest) [Read the full documentation here](http://docker-py.readthedocs.org/en/latest/). The source is available in the `docs/` directory. License ------- Docker is licensed under the Apache License, Version 2.0. See LICENSE for full license text docker-py-1.8.0/setup.cfg0000644000175000017500000000003512702731056013663 0ustar kickkick[bdist_wheel] universal = 1 docker-py-1.8.0/.travis.yml0000644000175000017500000000032212702731056014152 0ustar kickkicksudo: false language: python python: - "2.7" env: - TOX_ENV=py26 - TOX_ENV=py27 - TOX_ENV=py33 - TOX_ENV=py34 - TOX_ENV=flake8 install: - pip install tox script: - tox -e $TOX_ENV docker-py-1.8.0/requirements.txt0000644000175000017500000000014112702736203015323 0ustar kickkickrequests==2.5.3 six>=1.4.0 websocket-client==0.32.0 py2-ipaddress==3.4.1 ; python_version < '3.2'docker-py-1.8.0/setup.py0000644000175000017500000000266112702736203013562 0ustar kickkick#!/usr/bin/env python import os import sys from setuptools import setup ROOT_DIR = os.path.dirname(__file__) SOURCE_DIR = os.path.join(ROOT_DIR) requirements = [ 'requests >= 2.5.2', 'six >= 1.4.0', 'websocket-client >= 0.32.0', ] extras_require = { ':python_version < "3"': 'py2-ipaddress >= 3.4.1', } exec(open('docker/version.py').read()) with open('./test-requirements.txt') as test_reqs_txt: test_requirements = [line for line in test_reqs_txt] setup( name="docker-py", version=version, description="Python client for Docker.", url='https://github.com/docker/docker-py/', packages=[ 'docker', 'docker.api', 'docker.auth', 'docker.unixconn', 'docker.utils', 'docker.utils.ports', 'docker.ssladapter' ], install_requires=requirements, tests_require=test_requirements, extras_require=extras_require, zip_safe=False, test_suite='tests', classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Other Environment', 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Utilities', 'License :: OSI Approved :: Apache Software License', ], ) docker-py-1.8.0/test-requirements.txt0000644000175000017500000000011112702736203016275 0ustar kickkickmock==1.0.1 pytest==2.7.2 coverage==3.7.1 pytest-cov==2.1.0 flake8==2.4.1