diff options
author | Ben Firshman <ben@firshman.co.uk> | 2016-12-02 14:35:43 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-02 14:35:43 +0000 |
commit | ff6601cf64d8a8296d851b0d8ac44ec34ed46cca (patch) | |
tree | add38dbbce53d5356bd9dcf59da8bee3ac2edeca | |
parent | 01c33c0f684868290249945f9db9e00d7f03ecd9 (diff) | |
parent | 993f298e85539751343048467004a679414426ec (diff) | |
download | docker-py-ff6601cf64d8a8296d851b0d8ac44ec34ed46cca.tar.gz |
Merge pull request #1319 from docker/config_types
Move config type creation from docker.utils functions to classes in docker.types
-rw-r--r-- | docker/api/container.py | 16 | ||||
-rw-r--r-- | docker/api/network.py | 7 | ||||
-rw-r--r-- | docker/api/swarm.py | 3 | ||||
-rw-r--r-- | docker/models/containers.py | 4 | ||||
-rw-r--r-- | docker/models/networks.py | 6 | ||||
-rw-r--r-- | docker/types/__init__.py | 5 | ||||
-rw-r--r-- | docker/types/containers.py | 490 | ||||
-rw-r--r-- | docker/types/networks.py | 104 | ||||
-rw-r--r-- | docker/types/services.py | 143 | ||||
-rw-r--r-- | docker/utils/__init__.py | 9 | ||||
-rw-r--r-- | docker/utils/types.py | 7 | ||||
-rw-r--r-- | docker/utils/utils.py | 611 | ||||
-rw-r--r-- | docs/api.rst | 25 | ||||
-rw-r--r-- | tests/integration/api_container_test.py | 8 | ||||
-rw-r--r-- | tests/integration/api_network_test.py | 23 | ||||
-rw-r--r-- | tests/unit/api_network_test.py | 8 | ||||
-rw-r--r-- | tests/unit/dockertypes_test.py | 255 | ||||
-rw-r--r-- | tests/unit/utils_test.py | 248 |
18 files changed, 999 insertions, 973 deletions
diff --git a/docker/api/container.py b/docker/api/container.py index 10d7ff4..afe696c 100644 --- a/docker/api/container.py +++ b/docker/api/container.py @@ -4,7 +4,9 @@ from datetime import datetime from .. import errors from .. import utils -from ..utils.utils import create_networking_config, create_endpoint_config +from ..types import ( + ContainerConfig, EndpointConfig, HostConfig, NetworkingConfig +) class ContainerApiMixin(object): @@ -430,8 +432,8 @@ class ContainerApiMixin(object): ) config = self.create_container_config( - image, command, hostname, user, detach, stdin_open, - tty, mem_limit, ports, environment, dns, volumes, volumes_from, + image, command, hostname, user, detach, stdin_open, tty, mem_limit, + ports, dns, environment, 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, healthcheck, @@ -439,7 +441,7 @@ class ContainerApiMixin(object): return self.create_container_from_config(config, name) def create_container_config(self, *args, **kwargs): - return utils.create_container_config(self._version, *args, **kwargs) + return ContainerConfig(self._version, *args, **kwargs) def create_container_from_config(self, config, name=None): u = self._url("/containers/create") @@ -582,7 +584,7 @@ class ContainerApiMixin(object): "keyword argument 'version'" ) kwargs['version'] = self._version - return utils.create_host_config(*args, **kwargs) + return HostConfig(*args, **kwargs) def create_networking_config(self, *args, **kwargs): """ @@ -608,7 +610,7 @@ class ContainerApiMixin(object): ) """ - return create_networking_config(*args, **kwargs) + return NetworkingConfig(*args, **kwargs) def create_endpoint_config(self, *args, **kwargs): """ @@ -641,7 +643,7 @@ class ContainerApiMixin(object): ) """ - return create_endpoint_config(self._version, *args, **kwargs) + return EndpointConfig(self._version, *args, **kwargs) @utils.check_resource def diff(self, container): diff --git a/docker/api/network.py b/docker/api/network.py index 65aeb30..33da7ea 100644 --- a/docker/api/network.py +++ b/docker/api/network.py @@ -46,8 +46,7 @@ class NetworkApiMixin(object): name (str): Name of the network driver (str): Name of the driver used to create the network options (dict): Driver options as a key-value dictionary - ipam (dict): Optional custom IP scheme for the network. - Created with :py:meth:`~docker.utils.create_ipam_config`. + ipam (IPAMConfig): Optional custom IP scheme for the network. check_duplicate (bool): Request daemon to check for networks with same name. Default: ``True``. internal (bool): Restrict external access to the network. Default @@ -74,11 +73,11 @@ class NetworkApiMixin(object): .. code-block:: python - >>> ipam_pool = docker.utils.create_ipam_pool( + >>> ipam_pool = docker.types.IPAMPool( subnet='192.168.52.0/24', gateway='192.168.52.254' ) - >>> ipam_config = docker.utils.create_ipam_config( + >>> ipam_config = docker.types.IPAMConfig( pool_configs=[ipam_pool] ) >>> docker_client.create_network("network1", driver="bridge", diff --git a/docker/api/swarm.py b/docker/api/swarm.py index 521076f..6a1b752 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -1,5 +1,6 @@ import logging from six.moves import http_client +from .. import types from .. import utils log = logging.getLogger(__name__) @@ -50,7 +51,7 @@ class SwarmApiMixin(object): force_new_cluster=False, swarm_spec=spec ) """ - return utils.SwarmSpec(*args, **kwargs) + return types.SwarmSpec(*args, **kwargs) @utils.minimum_version('1.24') def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377', diff --git a/docker/models/containers.py b/docker/models/containers.py index 9682248..ad1cb61 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -2,7 +2,7 @@ import copy from ..errors import (ContainerError, ImageNotFound, create_unexpected_kwargs_error) -from ..utils import create_host_config +from ..types import HostConfig from .images import Image from .resource import Collection, Model @@ -869,7 +869,7 @@ def _create_container_args(kwargs): if kwargs: raise create_unexpected_kwargs_error('run', kwargs) - create_kwargs['host_config'] = create_host_config(**host_config_kwargs) + create_kwargs['host_config'] = HostConfig(**host_config_kwargs) # Fill in any kwargs which need processing by create_host_config first port_bindings = create_kwargs['host_config'].get('PortBindings') diff --git a/docker/models/networks.py b/docker/models/networks.py index 64af9ad..d5e2097 100644 --- a/docker/models/networks.py +++ b/docker/models/networks.py @@ -98,7 +98,7 @@ class NetworkCollection(Collection): driver (str): Name of the driver used to create the network options (dict): Driver options as a key-value dictionary ipam (dict): Optional custom IP scheme for the network. - Created with :py:meth:`~docker.utils.create_ipam_config`. + Created with :py:class:`~docker.types.IPAMConfig`. check_duplicate (bool): Request daemon to check for networks with same name. Default: ``True``. internal (bool): Restrict external access to the network. Default @@ -125,11 +125,11 @@ class NetworkCollection(Collection): .. code-block:: python - >>> ipam_pool = docker.utils.create_ipam_pool( + >>> ipam_pool = docker.types.IPAMPool( subnet='192.168.52.0/24', gateway='192.168.52.254' ) - >>> ipam_config = docker.utils.create_ipam_config( + >>> ipam_config = docker.types.IPAMConfig( pool_configs=[ipam_pool] ) >>> client.networks.create( diff --git a/docker/types/__init__.py b/docker/types/__init__.py index a7c3a56..7230723 100644 --- a/docker/types/__init__.py +++ b/docker/types/__init__.py @@ -1,8 +1,9 @@ # flake8: noqa -from .containers import LogConfig, Ulimit +from .containers import ContainerConfig, HostConfig, LogConfig, Ulimit +from .healthcheck import Healthcheck +from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig from .services import ( ContainerSpec, DriverConfig, EndpointSpec, Mount, Resources, RestartPolicy, TaskTemplate, UpdateConfig ) -from .healthcheck import Healthcheck from .swarm import SwarmSpec, SwarmExternalCA diff --git a/docker/types/containers.py b/docker/types/containers.py index 40a44ca..8fdecb3 100644 --- a/docker/types/containers.py +++ b/docker/types/containers.py @@ -1,6 +1,14 @@ import six +import warnings +from .. import errors +from ..utils.utils import ( + convert_port_bindings, convert_tmpfs_mounts, convert_volume_binds, + format_environment, normalize_links, parse_bytes, parse_devices, + split_command, version_gte, version_lt, +) from .base import DictType +from .healthcheck import Healthcheck class LogConfigTypesEnum(object): @@ -90,3 +98,485 @@ class Ulimit(DictType): @hard.setter def hard(self, value): self['Hard'] = value + + +class HostConfig(dict): + def __init__(self, version, 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_reservation=None, + kernel_memory=None, mem_swappiness=None, cgroup_parent=None, + group_add=None, cpu_quota=None, cpu_period=None, + blkio_weight=None, blkio_weight_device=None, + device_read_bps=None, device_write_bps=None, + device_read_iops=None, device_write_iops=None, + oom_kill_disable=False, shm_size=None, sysctls=None, + tmpfs=None, oom_score_adj=None, dns_opt=None, cpu_shares=None, + cpuset_cpus=None, userns_mode=None, pids_limit=None, + isolation=None): + + if mem_limit is not None: + self['Memory'] = parse_bytes(mem_limit) + + if memswap_limit is not None: + self['MemorySwap'] = parse_bytes(memswap_limit) + + if mem_reservation: + if version_lt(version, '1.21'): + raise host_config_version_error('mem_reservation', '1.21') + + self['MemoryReservation'] = parse_bytes(mem_reservation) + + if kernel_memory: + if version_lt(version, '1.21'): + raise host_config_version_error('kernel_memory', '1.21') + + self['KernelMemory'] = parse_bytes(kernel_memory) + + 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' + ) + + self['MemorySwappiness'] = mem_swappiness + + if shm_size is not None: + if isinstance(shm_size, six.string_types): + shm_size = parse_bytes(shm_size) + + self['ShmSize'] = shm_size + + if pid_mode: + if version_lt(version, '1.24') and pid_mode != 'host': + raise host_config_value_error('pid_mode', pid_mode) + self['PidMode'] = pid_mode + + if ipc_mode: + self['IpcMode'] = ipc_mode + + if privileged: + self['Privileged'] = privileged + + if oom_kill_disable: + if version_lt(version, '1.20'): + raise host_config_version_error('oom_kill_disable', '1.19') + + self['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' + ) + self['OomScoreAdj'] = oom_score_adj + + if publish_all_ports: + self['PublishAllPorts'] = publish_all_ports + + if read_only is not None: + self['ReadonlyRootfs'] = read_only + + if dns_search: + self['DnsSearch'] = dns_search + + if network_mode: + self['NetworkMode'] = network_mode + elif network_mode is None and version_gte(version, '1.20'): + self['NetworkMode'] = 'default' + + if restart_policy: + if not isinstance(restart_policy, dict): + raise host_config_type_error( + 'restart_policy', restart_policy, 'dict' + ) + + self['RestartPolicy'] = restart_policy + + if cap_add: + self['CapAdd'] = cap_add + + if cap_drop: + self['CapDrop'] = cap_drop + + if devices: + self['Devices'] = parse_devices(devices) + + if group_add: + if version_lt(version, '1.20'): + raise host_config_version_error('group_add', '1.20') + + self['GroupAdd'] = [six.text_type(grp) for grp in group_add] + + if dns is not None: + self['Dns'] = dns + + if dns_opt is not None: + if version_lt(version, '1.21'): + raise host_config_version_error('dns_opt', '1.21') + + self['DnsOptions'] = dns_opt + + if security_opt is not None: + if not isinstance(security_opt, list): + raise host_config_type_error( + 'security_opt', security_opt, 'list' + ) + + self['SecurityOpt'] = security_opt + + if sysctls: + if not isinstance(sysctls, dict): + raise host_config_type_error('sysctls', sysctls, 'dict') + self['Sysctls'] = {} + for k, v in six.iteritems(sysctls): + self['Sysctls'][k] = six.text_type(v) + + if volumes_from is not None: + if isinstance(volumes_from, six.string_types): + volumes_from = volumes_from.split(',') + + self['VolumesFrom'] = volumes_from + + if binds is not None: + self['Binds'] = convert_volume_binds(binds) + + if port_bindings is not None: + self['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)) + ] + + self['ExtraHosts'] = extra_hosts + + if links is not None: + self['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: + self['LxcConf'] = lxc_conf + + if cgroup_parent is not None: + self['CgroupParent'] = cgroup_parent + + if ulimits is not None: + if not isinstance(ulimits, list): + raise host_config_type_error('ulimits', ulimits, 'list') + self['Ulimits'] = [] + for l in ulimits: + if not isinstance(l, Ulimit): + l = Ulimit(**l) + self['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) + + self['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') + + self['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') + + self['CpuPeriod'] = cpu_period + + if cpu_shares: + if version_lt(version, '1.18'): + raise host_config_version_error('cpu_shares', '1.18') + + if not isinstance(cpu_shares, int): + raise host_config_type_error('cpu_shares', cpu_shares, 'int') + + self['CpuShares'] = cpu_shares + + if cpuset_cpus: + if version_lt(version, '1.18'): + raise host_config_version_error('cpuset_cpus', '1.18') + + self['CpuSetCpus'] = cpuset_cpus + + if blkio_weight: + if not isinstance(blkio_weight, int): + raise host_config_type_error( + 'blkio_weight', blkio_weight, 'int' + ) + if version_lt(version, '1.22'): + raise host_config_version_error('blkio_weight', '1.22') + self["BlkioWeight"] = blkio_weight + + if blkio_weight_device: + if not isinstance(blkio_weight_device, list): + raise host_config_type_error( + 'blkio_weight_device', blkio_weight_device, 'list' + ) + if version_lt(version, '1.22'): + raise host_config_version_error('blkio_weight_device', '1.22') + self["BlkioWeightDevice"] = blkio_weight_device + + if device_read_bps: + if not isinstance(device_read_bps, list): + raise host_config_type_error( + 'device_read_bps', device_read_bps, 'list' + ) + if version_lt(version, '1.22'): + raise host_config_version_error('device_read_bps', '1.22') + self["BlkioDeviceReadBps"] = device_read_bps + + if device_write_bps: + if not isinstance(device_write_bps, list): + raise host_config_type_error( + 'device_write_bps', device_write_bps, 'list' + ) + if version_lt(version, '1.22'): + raise host_config_version_error('device_write_bps', '1.22') + self["BlkioDeviceWriteBps"] = device_write_bps + + if device_read_iops: + if not isinstance(device_read_iops, list): + raise host_config_type_error( + 'device_read_iops', device_read_iops, 'list' + ) + if version_lt(version, '1.22'): + raise host_config_version_error('device_read_iops', '1.22') + self["BlkioDeviceReadIOps"] = device_read_iops + + if device_write_iops: + if not isinstance(device_write_iops, list): + raise host_config_type_error( + 'device_write_iops', device_write_iops, 'list' + ) + if version_lt(version, '1.22'): + raise host_config_version_error('device_write_iops', '1.22') + self["BlkioDeviceWriteIOps"] = device_write_iops + + if tmpfs: + if version_lt(version, '1.22'): + raise host_config_version_error('tmpfs', '1.22') + self["Tmpfs"] = convert_tmpfs_mounts(tmpfs) + + if userns_mode: + if version_lt(version, '1.23'): + raise host_config_version_error('userns_mode', '1.23') + + if userns_mode != "host": + raise host_config_value_error("userns_mode", userns_mode) + self['UsernsMode'] = userns_mode + + if pids_limit: + if not isinstance(pids_limit, int): + raise host_config_type_error('pids_limit', pids_limit, 'int') + if version_lt(version, '1.23'): + raise host_config_version_error('pids_limit', '1.23') + self["PidsLimit"] = pids_limit + + if isolation: + if not isinstance(isolation, six.string_types): + raise host_config_type_error('isolation', isolation, 'string') + if version_lt(version, '1.24'): + raise host_config_version_error('isolation', '1.24') + self['Isolation'] = isolation + + +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)) + + +class ContainerConfig(dict): + def __init__( + self, version, image, command, hostname=None, user=None, detach=False, + stdin_open=False, tty=False, mem_limit=None, ports=None, dns=None, + environment=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, healthcheck=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 version_lt(version, '1.18'): + raise errors.InvalidVersion( + 'labels were only introduced in API version 1.18' + ) + + if cpuset is not None or cpu_shares is not None: + if version_gte(version, '1.18'): + warnings.warn( + 'The cpuset_cpus and cpu_shares options have been moved to' + ' host_config in API version 1.18, and will be removed', + DeprecationWarning + ) + + if stop_signal is not None and version_lt(version, '1.21'): + raise errors.InvalidVersion( + 'stop_signal was only introduced in API version 1.21' + ) + + if healthcheck is not None and version_lt(version, '1.24'): + raise errors.InvalidVersion( + 'Health options were only introduced in API version 1.24' + ) + + if version_lt(version, '1.19'): + 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 + + if healthcheck and isinstance(healthcheck, dict): + healthcheck = Healthcheck(**healthcheck) + + 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 version_gte(version, '1.10'): + 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')) + + self.update({ + '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, + 'Healthcheck': healthcheck, + }) diff --git a/docker/types/networks.py b/docker/types/networks.py new file mode 100644 index 0000000..a539ac0 --- /dev/null +++ b/docker/types/networks.py @@ -0,0 +1,104 @@ +from .. import errors +from ..utils import normalize_links, version_lt + + +class EndpointConfig(dict): + def __init__(self, version, aliases=None, links=None, ipv4_address=None, + ipv6_address=None, link_local_ips=None): + if version_lt(version, '1.22'): + raise errors.InvalidVersion( + 'Endpoint config is not supported for API version < 1.22' + ) + + if aliases: + self["Aliases"] = aliases + + if links: + self["Links"] = normalize_links(links) + + ipam_config = {} + if ipv4_address: + ipam_config['IPv4Address'] = ipv4_address + + if ipv6_address: + ipam_config['IPv6Address'] = ipv6_address + + if link_local_ips is not None: + if version_lt(version, '1.24'): + raise errors.InvalidVersion( + 'link_local_ips is not supported for API version < 1.24' + ) + ipam_config['LinkLocalIPs'] = link_local_ips + + if ipam_config: + self['IPAMConfig'] = ipam_config + + +class NetworkingConfig(dict): + def __init__(self, endpoints_config=None): + if endpoints_config: + self["EndpointsConfig"] = endpoints_config + + +class IPAMConfig(dict): + """ + Create an IPAM (IP Address Management) config dictionary to be used with + :py:meth:`~docker.api.network.NetworkApiMixin.create_network`. + + Args: + + driver (str): The IPAM driver to use. Defaults to ``default``. + pool_configs (list): A list of pool configurations + (:py:class:`~docker.types.IPAMPool`). Defaults to empty list. + + Example: + + >>> ipam_config = docker.types.IPAMConfig(driver='default') + >>> network = client.create_network('network1', ipam=ipam_config) + + """ + def __init__(self, driver='default', pool_configs=None): + self.update({ + 'Driver': driver, + 'Config': pool_configs or [] + }) + + +class IPAMPool(dict): + """ + Create an IPAM pool config dictionary to be added to the + ``pool_configs`` parameter of + :py:class:`~docker.types.IPAMConfig`. + + Args: + + subnet (str): Custom subnet for this IPAM pool using the CIDR + notation. Defaults to ``None``. + iprange (str): Custom IP range for endpoints in this IPAM pool using + the CIDR notation. Defaults to ``None``. + gateway (str): Custom IP address for the pool's gateway. + aux_addresses (dict): A dictionary of ``key -> ip_address`` + relationships specifying auxiliary addresses that need to be + allocated by the IPAM driver. + + Example: + + >>> ipam_pool = docker.types.IPAMPool( + subnet='124.42.0.0/16', + iprange='124.42.0.0/24', + gateway='124.42.0.254', + aux_addresses={ + 'reserved1': '124.42.1.1' + } + ) + >>> ipam_config = docker.types.IPAMConfig( + pool_configs=[ipam_pool]) + """ + def __init__(self, subnet=None, iprange=None, gateway=None, + aux_addresses=None): + self.update({ + 'Subnet': subnet, + 'IPRange': iprange, + 'Gateway': gateway, + 'AuxiliaryAddresses': aux_addresses + }) diff --git a/docker/types/services.py b/docker/types/services.py index 9d5fa1b..82d1e60 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -1,6 +1,7 @@ import six from .. import errors +from ..utils import split_command class TaskTemplate(dict): @@ -10,19 +11,15 @@ class TaskTemplate(dict): Args: - * container_spec (dict): Container settings for containers started as part - of this task. See the :py:class:`~docker.types.services.ContainerSpec` - for details. - * log_driver (dict): Log configuration for containers created as part of - the service. See the :py:class:`~docker.types.services.DriverConfig` - class for details. - * resources (dict): Resource requirements which apply to each individual - container created as part of the service. See the - :py:class:`~docker.types.services.Resources` class for details. - * restart_policy (dict): Specification for the restart policy which applies - to containers created as part of this service. See the - :py:class:`~docker.types.services.RestartPolicy` class for details. - * placement (list): A list of constraints. + container_spec (ContainerSpec): Container settings for containers + started as part of this task. + log_driver (DriverConfig): Log configuration for containers created as + part of the service. + resources (Resources): Resource requirements which apply to each + individual container created as part of the service. + restart_policy (RestartPolicy): Specification for the restart policy + which applies to containers created as part of this service. + placement (list): A list of constraints. """ def __init__(self, container_spec, resources=None, restart_policy=None, placement=None, log_driver=None): @@ -58,27 +55,25 @@ class TaskTemplate(dict): class ContainerSpec(dict): """ Describes the behavior of containers that are part of a task, and is used - when declaring a :py:class:`~docker.types.services.TaskTemplate`. + when declaring a :py:class:`~docker.types.TaskTemplate`. Args: - * image (string): The image name to use for the container. - * command (string or list): The command to be run in the image. - * args (list): Arguments to the command. - * env (dict): Environment variables. - * dir (string): The working directory for commands to run in. - * user (string): The user inside the container. - * labels (dict): A map of labels to associate with the service. - * mounts (list): A list of specifications for mounts to be added to - containers created as part of the service. See the - :py:class:`~docker.types.services.Mount` class for details. - * stop_grace_period (int): Amount of time to wait for the container to - terminate before forcefully killing it. + image (string): The image name to use for the container. + command (string or list): The command to be run in the image. + args (list): Arguments to the command. + env (dict): Environment variables. + dir (string): The working directory for commands to run in. + user (string): The user inside the container. + labels (dict): A map of labels to associate with the service. + mounts (list): A list of specifications for mounts to be added to + containers created as part of the service. See the + :py:class:`~docker.types.Mount` class for details. + stop_grace_period (int): Amount of time to wait for the container to + terminate before forcefully killing it. """ def __init__(self, image, command=None, args=None, env=None, workdir=None, user=None, labels=None, mounts=None, stop_grace_period=None): - from ..utils import split_command # FIXME: circular import - self['Image'] = image if isinstance(command, six.string_types): @@ -108,24 +103,24 @@ class Mount(dict): """ Describes a mounted folder's configuration inside a container. A list of ``Mount``s would be used as part of a - :py:class:`~docker.types.services.ContainerSpec`. + :py:class:`~docker.types.ContainerSpec`. Args: - * target (string): Container path. - * source (string): Mount source (e.g. a volume name or a host path). - * type (string): The mount type (``bind`` or ``volume``). - Default: ``volume``. - * read_only (bool): Whether the mount should be read-only. - * propagation (string): A propagation mode with the value ``[r]private``, - ``[r]shared``, or ``[r]slave``. Only valid for the ``bind`` type. - * no_copy (bool): False if the volume should be populated with the data - from the target. Default: ``False``. Only valid for the ``volume`` type. - * labels (dict): User-defined name and labels for the volume. Only valid - for the ``volume`` type. - * driver_config (dict): Volume driver configuration. - See the :py:class:`~docker.types.services.DriverConfig` class for - details. Only valid for the ``volume`` type. + target (string): Container path. + source (string): Mount source (e.g. a volume name or a host path). + type (string): The mount type (``bind`` or ``volume``). + Default: ``volume``. + read_only (bool): Whether the mount should be read-only. + propagation (string): A propagation mode with the value ``[r]private``, + ``[r]shared``, or ``[r]slave``. Only valid for the ``bind`` type. + no_copy (bool): False if the volume should be populated with the data + from the target. Default: ``False``. Only valid for the ``volume`` + type. + labels (dict): User-defined name and labels for the volume. Only valid + for the ``volume`` type. + driver_config (DriverConfig): Volume driver configuration. Only valid + for the ``volume`` type. """ def __init__(self, target, source, type='volume', read_only=False, propagation=None, no_copy=False, labels=None, @@ -183,14 +178,14 @@ class Mount(dict): class Resources(dict): """ Configures resource allocation for containers when made part of a - :py:class:`~docker.types.services.ContainerSpec`. + :py:class:`~docker.types.ContainerSpec`. Args: - * cpu_limit (int): CPU limit in units of 10^9 CPU shares. - * mem_limit (int): Memory limit in Bytes. - * cpu_reservation (int): CPU reservation in units of 10^9 CPU shares. - * mem_reservation (int): Memory reservation in Bytes. + cpu_limit (int): CPU limit in units of 10^9 CPU shares. + mem_limit (int): Memory limit in Bytes. + cpu_reservation (int): CPU reservation in units of 10^9 CPU shares. + mem_reservation (int): Memory reservation in Bytes. """ def __init__(self, cpu_limit=None, mem_limit=None, cpu_reservation=None, mem_reservation=None): @@ -218,12 +213,12 @@ class UpdateConfig(dict): Args: - * parallelism (int): Maximum number of tasks to be updated in one iteration - (0 means unlimited parallelism). Default: 0. - * delay (int): Amount of time between updates. - * failure_action (string): Action to take if an updated task fails to run, - or stops running during the update. Acceptable values are ``continue`` - and ``pause``. Default: ``continue`` + parallelism (int): Maximum number of tasks to be updated in one + iteration (0 means unlimited parallelism). Default: 0. + delay (int): Amount of time between updates. + failure_action (string): Action to take if an updated task fails to + run, or stops running during the update. Acceptable values are + ``continue`` and ``pause``. Default: ``continue`` """ def __init__(self, parallelism=0, delay=None, failure_action='continue'): self['Parallelism'] = parallelism @@ -247,16 +242,18 @@ class RestartConditionTypesEnum(object): class RestartPolicy(dict): """ - Used when creating a :py:class:`~docker.types.services.ContainerSpec`, + Used when creating a :py:class:`~docker.types.ContainerSpec`, dictates whether a container should restart after stopping or failing. - * condition (string): Condition for restart (``none``, ``on-failure``, - or ``any``). Default: `none`. - * delay (int): Delay between restart attempts. Default: 0 - * attempts (int): Maximum attempts to restart a given container before - giving up. Default value is 0, which is ignored. - * window (int): Time window used to evaluate the restart policy. Default - value is 0, which is unbounded. + Args: + + condition (string): Condition for restart (``none``, ``on-failure``, + or ``any``). Default: `none`. + delay (int): Delay between restart attempts. Default: 0 + attempts (int): Maximum attempts to restart a given container before + giving up. Default value is 0, which is ignored. + window (int): Time window used to evaluate the restart policy. Default + value is 0, which is unbounded. """ condition_types = RestartConditionTypesEnum @@ -277,14 +274,14 @@ class RestartPolicy(dict): class DriverConfig(dict): """ Indicates which driver to use, as well as its configuration. Can be used - as ``log_driver`` in a :py:class:`~docker.types.services.ContainerSpec`, + as ``log_driver`` in a :py:class:`~docker.types.ContainerSpec`, and for the `driver_config` in a volume - :py:class:`~docker.types.services.Mount`. + :py:class:`~docker.types.Mount`. Args: - * name (string): Name of the driver to use. - * options (dict): Driver-specific options. Default: ``None``. + name (string): Name of the driver to use. + options (dict): Driver-specific options. Default: ``None``. """ def __init__(self, name, options=None): self['Name'] = name @@ -298,13 +295,13 @@ class EndpointSpec(dict): Args: - * mode (string): The mode of resolution to use for internal load balancing - between tasks (``'vip'`` or ``'dnsrr'``). Defaults to ``'vip'`` if not - provided. - * ports (dict): Exposed ports that this service is accessible on from the - outside, in the form of ``{ target_port: published_port }`` or - ``{ target_port: (published_port, protocol) }``. Ports can only be - provided if the ``vip`` resolution mode is used. + mode (string): The mode of resolution to use for internal load + balancing between tasks (``'vip'`` or ``'dnsrr'``). Defaults to + ``'vip'`` if not provided. + ports (dict): Exposed ports that this service is accessible on from the + outside, in the form of ``{ target_port: published_port }`` or + ``{ target_port: (published_port, protocol) }``. Ports can only be + provided if the ``vip`` resolution mode is used. """ def __init__(self, mode=None, ports=None): if ports: diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py index 5bd69b4..061c26e 100644 --- a/docker/utils/__init__.py +++ b/docker/utils/__init__.py @@ -3,12 +3,9 @@ from .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, - convert_service_networks, + create_host_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, convert_service_networks, ) -from ..types import LogConfig, Ulimit -from ..types import SwarmExternalCA, SwarmSpec from .decorators import check_resource, minimum_version, update_headers diff --git a/docker/utils/types.py b/docker/utils/types.py deleted file mode 100644 index 8098c47..0000000 --- a/docker/utils/types.py +++ /dev/null @@ -1,7 +0,0 @@ -# Compatibility module. See https://github.com/docker/docker-py/issues/1196 - -import warnings - -from ..types import Ulimit, LogConfig # flake8: noqa - -warnings.warn('docker.utils.types is now docker.types', ImportWarning) diff --git a/docker/utils/utils.py b/docker/utils/utils.py index a6ebf0f..4e5f454 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -15,10 +15,8 @@ from fnmatch import fnmatch import requests import six -from .. import constants from .. import errors from .. import tls -from ..types import Ulimit, LogConfig, Healthcheck if six.PY2: from urllib import splitnport @@ -37,72 +35,18 @@ BYTE_UNITS = { } -def create_ipam_pool(subnet=None, iprange=None, gateway=None, - aux_addresses=None): - """ - Create an IPAM pool config dictionary to be added to the - ``pool_configs`` parameter of - :py:meth:`~docker.utils.create_ipam_config`. - - Args: - - subnet (str): Custom subnet for this IPAM pool using the CIDR - notation. Defaults to ``None``. - iprange (str): Custom IP range for endpoints in this IPAM pool using - the CIDR notation. Defaults to ``None``. - gateway (str): Custom IP address for the pool's gateway. - aux_addresses (dict): A dictionary of ``key -> ip_address`` - relationships specifying auxiliary addresses that need to be - allocated by the IPAM driver. - - Returns: - (dict) An IPAM pool config - - Example: - - >>> ipam_pool = docker.utils.create_ipam_pool( - subnet='124.42.0.0/16', - iprange='124.42.0.0/24', - gateway='124.42.0.254', - aux_addresses={ - 'reserved1': '124.42.1.1' - } - ) - >>> ipam_config = docker.utils.create_ipam_config( - pool_configs=[ipam_pool]) - """ - return { - 'Subnet': subnet, - 'IPRange': iprange, - 'Gateway': gateway, - 'AuxiliaryAddresses': aux_addresses - } - - -def create_ipam_config(driver='default', pool_configs=None): - """ - Create an IPAM (IP Address Management) config dictionary to be used with - :py:meth:`~docker.api.network.NetworkApiMixin.create_network`. - - Args: - driver (str): The IPAM driver to use. Defaults to ``default``. - pool_configs (list): A list of pool configuration dictionaries as - created by :py:meth:`~docker.utils.create_ipam_pool`. Defaults to - empty list. - - Returns: - (dict) An IPAM config. - - Example: +def create_ipam_pool(*args, **kwargs): + raise errors.DeprecatedMethod( + 'utils.create_ipam_pool has been removed. Please use a ' + 'docker.types.IPAMPool object instead.' + ) - >>> ipam_config = docker.utils.create_ipam_config(driver='default') - >>> network = client.create_network('network1', ipam=ipam_config) - """ - return { - 'Driver': driver, - 'Config': pool_configs or [] - } +def create_ipam_config(*args, **kwargs): + raise errors.DeprecatedMethod( + 'utils.create_ipam_config has been removed. Please use a ' + 'docker.types.IPAMConfig object instead.' + ) def mkbuildcontext(dockerfile): @@ -669,338 +613,6 @@ def parse_bytes(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_reservation=None, kernel_memory=None, - mem_swappiness=None, cgroup_parent=None, - group_add=None, cpu_quota=None, - cpu_period=None, blkio_weight=None, - blkio_weight_device=None, device_read_bps=None, - device_write_bps=None, device_read_iops=None, - device_write_iops=None, oom_kill_disable=False, - shm_size=None, sysctls=None, version=None, tmpfs=None, - oom_score_adj=None, dns_opt=None, cpu_shares=None, - cpuset_cpus=None, userns_mode=None, pids_limit=None, - isolation=None): - - host_config = {} - - if not version: - warnings.warn( - 'docker.utils.create_host_config() is deprecated. Please use ' - 'APIClient.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_reservation: - if version_lt(version, '1.21'): - raise host_config_version_error('mem_reservation', '1.21') - - host_config['MemoryReservation'] = parse_bytes(mem_reservation) - - if kernel_memory: - if version_lt(version, '1.21'): - raise host_config_version_error('kernel_memory', '1.21') - - host_config['KernelMemory'] = parse_bytes(kernel_memory) - - 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: - if version_lt(version, '1.24') and pid_mode != 'host': - raise host_config_value_error('pid_mode', 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 dns_opt is not None: - if version_lt(version, '1.21'): - raise host_config_version_error('dns_opt', '1.21') - - host_config['DnsOptions'] = dns_opt - - 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 sysctls: - if not isinstance(sysctls, dict): - raise host_config_type_error('sysctls', sysctls, 'dict') - host_config['Sysctls'] = {} - for k, v in six.iteritems(sysctls): - host_config['Sysctls'][k] = six.text_type(v) - - 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 cpu_shares: - if version_lt(version, '1.18'): - raise host_config_version_error('cpu_shares', '1.18') - - if not isinstance(cpu_shares, int): - raise host_config_type_error('cpu_shares', cpu_shares, 'int') - - host_config['CpuShares'] = cpu_shares - - if cpuset_cpus: - if version_lt(version, '1.18'): - raise host_config_version_error('cpuset_cpus', '1.18') - - host_config['CpuSetCpus'] = cpuset_cpus - - if blkio_weight: - if not isinstance(blkio_weight, int): - raise host_config_type_error('blkio_weight', blkio_weight, 'int') - if version_lt(version, '1.22'): - raise host_config_version_error('blkio_weight', '1.22') - host_config["BlkioWeight"] = blkio_weight - - if blkio_weight_device: - if not isinstance(blkio_weight_device, list): - raise host_config_type_error( - 'blkio_weight_device', blkio_weight_device, 'list' - ) - if version_lt(version, '1.22'): - raise host_config_version_error('blkio_weight_device', '1.22') - host_config["BlkioWeightDevice"] = blkio_weight_device - - if device_read_bps: - if not isinstance(device_read_bps, list): - raise host_config_type_error( - 'device_read_bps', device_read_bps, 'list' - ) - if version_lt(version, '1.22'): - raise host_config_version_error('device_read_bps', '1.22') - host_config["BlkioDeviceReadBps"] = device_read_bps - - if device_write_bps: - if not isinstance(device_write_bps, list): - raise host_config_type_error( - 'device_write_bps', device_write_bps, 'list' - ) - if version_lt(version, '1.22'): - raise host_config_version_error('device_write_bps', '1.22') - host_config["BlkioDeviceWriteBps"] = device_write_bps - - if device_read_iops: - if not isinstance(device_read_iops, list): - raise host_config_type_error( - 'device_read_iops', device_read_iops, 'list' - ) - if version_lt(version, '1.22'): - raise host_config_version_error('device_read_iops', '1.22') - host_config["BlkioDeviceReadIOps"] = device_read_iops - - if device_write_iops: - if not isinstance(device_write_iops, list): - raise host_config_type_error( - 'device_write_iops', device_write_iops, 'list' - ) - if version_lt(version, '1.22'): - raise host_config_version_error('device_write_iops', '1.22') - host_config["BlkioDeviceWriteIOps"] = device_write_iops - - if tmpfs: - if version_lt(version, '1.22'): - raise host_config_version_error('tmpfs', '1.22') - host_config["Tmpfs"] = convert_tmpfs_mounts(tmpfs) - - if userns_mode: - if version_lt(version, '1.23'): - raise host_config_version_error('userns_mode', '1.23') - - if userns_mode != "host": - raise host_config_value_error("userns_mode", userns_mode) - host_config['UsernsMode'] = userns_mode - - if pids_limit: - if not isinstance(pids_limit, int): - raise host_config_type_error('pids_limit', pids_limit, 'int') - if version_lt(version, '1.23'): - raise host_config_version_error('pids_limit', '1.23') - host_config["PidsLimit"] = pids_limit - - if isolation: - if not isinstance(isolation, six.string_types): - raise host_config_type_error('isolation', isolation, 'string') - if version_lt(version, '1.24'): - raise host_config_version_error('isolation', '1.24') - host_config['Isolation'] = isolation - - return host_config - - def normalize_links(links): if isinstance(links, dict): links = six.iteritems(links) @@ -1008,50 +620,6 @@ def normalize_links(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, - ipv4_address=None, ipv6_address=None, - link_local_ips=None): - if version_lt(version, '1.22'): - raise errors.InvalidVersion( - 'Endpoint config is not supported for API version < 1.22' - ) - endpoint_config = {} - - if aliases: - endpoint_config["Aliases"] = aliases - - if links: - endpoint_config["Links"] = normalize_links(links) - - ipam_config = {} - if ipv4_address: - ipam_config['IPv4Address'] = ipv4_address - - if ipv6_address: - ipam_config['IPv6Address'] = ipv6_address - - if link_local_ips is not None: - if version_lt(version, '1.24'): - raise errors.InvalidVersion( - 'link_local_ips is not supported for API version < 1.24' - ) - ipam_config['LinkLocalIPs'] = link_local_ips - - if ipam_config: - endpoint_config['IPAMConfig'] = ipam_config - - return endpoint_config - - def parse_env_file(env_file): """ Reads a line-separated environment file. @@ -1098,157 +666,8 @@ def format_environment(environment): 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, - healthcheck=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 cpuset is not None or cpu_shares is not None: - if version_gte(version, '1.18'): - warnings.warn( - 'The cpuset_cpus and cpu_shares options have been moved to ' - 'host_config in API version 1.18, and will be removed', - DeprecationWarning - ) - - 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 healthcheck is not None and version_lt(version, '1.24'): - raise errors.InvalidVersion( - 'Health options were only introduced in API version 1.24' - ) - - 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 - - if healthcheck and isinstance(healthcheck, dict): - healthcheck = Healthcheck(**healthcheck) - - 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, - 'Healthcheck': healthcheck, - } +def create_host_config(self, *args, **kwargs): + raise errors.DeprecatedMethod( + 'utils.create_host_config has been removed. Please use a ' + 'docker.types.HostConfig object instead.' + ) diff --git a/docs/api.rst b/docs/api.rst index 97db839..5e59aa7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -49,15 +49,6 @@ Networks :members: :undoc-members: -Utilities -~~~~~~~~~ - -These functions are available under ``docker.utils`` to create arguments -for :py:meth:`create_network`: - -.. autofunction:: docker.utils.create_ipam_config -.. autofunction:: docker.utils.create_ipam_pool - Volumes ------- @@ -107,3 +98,19 @@ The Docker daemon .. autoclass:: DaemonApiMixin :members: :undoc-members: + +Configuration types +------------------- + +.. py:module:: docker.types + +.. autoclass:: IPAMConfig +.. autoclass:: IPAMPool +.. autoclass:: ContainerSpec +.. autoclass:: DriverConfig +.. autoclass:: EndpointSpec +.. autoclass:: Mount +.. autoclass:: Resources +.. autoclass:: RestartPolicy +.. autoclass:: TaskTemplate +.. autoclass:: UpdateConfig diff --git a/tests/integration/api_container_test.py b/tests/integration/api_container_test.py index f09e75a..bebadb7 100644 --- a/tests/integration/api_container_test.py +++ b/tests/integration/api_container_test.py @@ -255,7 +255,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): self.assertIn('1001', groups) def test_valid_log_driver_and_log_opt(self): - log_config = docker.utils.LogConfig( + log_config = docker.types.LogConfig( type='json-file', config={'max-file': '100'} ) @@ -274,7 +274,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): self.assertEqual(container_log_config['Config'], log_config.config) def test_invalid_log_driver_raises_exception(self): - log_config = docker.utils.LogConfig( + log_config = docker.types.LogConfig( type='asdf-nope', config={} ) @@ -292,7 +292,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): assert excinfo.value.explanation == expected_msg def test_valid_no_log_driver_specified(self): - log_config = docker.utils.LogConfig( + log_config = docker.types.LogConfig( type="", config={'max-file': '100'} ) @@ -311,7 +311,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): self.assertEqual(container_log_config['Config'], log_config.config) def test_valid_no_config_specified(self): - log_config = docker.utils.LogConfig( + log_config = docker.types.LogConfig( type="json-file", config=None ) diff --git a/tests/integration/api_network_test.py b/tests/integration/api_network_test.py index 092a12c..b1ac52c 100644 --- a/tests/integration/api_network_test.py +++ b/tests/integration/api_network_test.py @@ -1,6 +1,5 @@ import docker -from docker.utils import create_ipam_config -from docker.utils import create_ipam_pool +from docker.types import IPAMConfig, IPAMPool import pytest from ..helpers import random_name, requires_api_version @@ -45,10 +44,10 @@ class TestNetworks(BaseAPIIntegrationTest): @requires_api_version('1.21') def test_create_network_with_ipam_config(self): _, net_id = self.create_network( - ipam=create_ipam_config( + ipam=IPAMConfig( driver='default', pool_configs=[ - create_ipam_pool( + IPAMPool( subnet="172.28.0.0/16", iprange="172.28.5.0/24", gateway="172.28.5.254", @@ -217,9 +216,9 @@ class TestNetworks(BaseAPIIntegrationTest): @requires_api_version('1.22') def test_create_with_ipv4_address(self): net_name, net_id = self.create_network( - ipam=create_ipam_config( + ipam=IPAMConfig( driver='default', - pool_configs=[create_ipam_pool(subnet="132.124.0.0/16")], + pool_configs=[IPAMPool(subnet="132.124.0.0/16")], ), ) container = self.client.create_container( @@ -246,9 +245,9 @@ class TestNetworks(BaseAPIIntegrationTest): @requires_api_version('1.22') def test_create_with_ipv6_address(self): net_name, net_id = self.create_network( - ipam=create_ipam_config( + ipam=IPAMConfig( driver='default', - pool_configs=[create_ipam_pool(subnet="2001:389::1/64")], + pool_configs=[IPAMPool(subnet="2001:389::1/64")], ), ) container = self.client.create_container( @@ -353,10 +352,10 @@ class TestNetworks(BaseAPIIntegrationTest): @requires_api_version('1.22') def test_connect_with_ipv4_address(self): net_name, net_id = self.create_network( - ipam=create_ipam_config( + ipam=IPAMConfig( driver='default', pool_configs=[ - create_ipam_pool( + IPAMPool( subnet="172.28.0.0/16", iprange="172.28.5.0/24", gateway="172.28.5.254" ) @@ -381,10 +380,10 @@ class TestNetworks(BaseAPIIntegrationTest): @requires_api_version('1.22') def test_connect_with_ipv6_address(self): net_name, net_id = self.create_network( - ipam=create_ipam_config( + ipam=IPAMConfig( driver='default', pool_configs=[ - create_ipam_pool( + IPAMPool( subnet="2001:389::1/64", iprange="2001:389::0/96", gateway="2001:389::ffff" ) diff --git a/tests/unit/api_network_test.py b/tests/unit/api_network_test.py index 8e09c67..037edb5 100644 --- a/tests/unit/api_network_test.py +++ b/tests/unit/api_network_test.py @@ -4,7 +4,7 @@ import six from .api_test import BaseAPIClientTest, url_prefix, response from ..helpers import requires_api_version -from docker.utils import create_ipam_config, create_ipam_pool +from docker.types import IPAMConfig, IPAMPool try: from unittest import mock @@ -81,9 +81,9 @@ class NetworkTest(BaseAPIClientTest): 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]) + ipam_pool_config = IPAMPool(subnet="192.168.52.0/24", + gateway="192.168.52.254") + ipam_config = IPAMConfig(pool_configs=[ipam_pool_config]) self.client.create_network("bar", driver="bridge", ipam=ipam_config) diff --git a/tests/unit/dockertypes_test.py b/tests/unit/dockertypes_test.py new file mode 100644 index 0000000..2480b9e --- /dev/null +++ b/tests/unit/dockertypes_test.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- + +import unittest + +import pytest + +from docker.constants import DEFAULT_DOCKER_API_VERSION +from docker.errors import InvalidVersion +from docker.types import ( + EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Ulimit, +) + + +def create_host_config(*args, **kwargs): + return HostConfig(*args, **kwargs) + + +class HostConfigTest(unittest.TestCase): + 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_blkio_constraints(self): + blkio_rate = [{"Path": "/dev/sda", "Rate": 1000}] + config = create_host_config(version='1.22', + blkio_weight=1999, + blkio_weight_device=blkio_rate, + device_read_bps=blkio_rate, + device_write_bps=blkio_rate, + device_read_iops=blkio_rate, + device_write_iops=blkio_rate) + + self.assertEqual(config.get('BlkioWeight'), 1999) + self.assertTrue(config.get('BlkioWeightDevice') is blkio_rate) + self.assertTrue(config.get('BlkioDeviceReadBps') is blkio_rate) + self.assertTrue(config.get('BlkioDeviceWriteBps') is blkio_rate) + self.assertTrue(config.get('BlkioDeviceReadIOps') is blkio_rate) + self.assertTrue(config.get('BlkioDeviceWriteIOps') is blkio_rate) + self.assertEqual(blkio_rate[0]['Path'], "/dev/sda") + self.assertEqual(blkio_rate[0]['Rate'], 1000) + + 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_userns_mode(self): + config = create_host_config(version='1.23', userns_mode='host') + self.assertEqual(config.get('UsernsMode'), 'host') + self.assertRaises( + InvalidVersion, lambda: create_host_config(version='1.22', + userns_mode='host')) + self.assertRaises( + ValueError, lambda: create_host_config(version='1.23', + userns_mode='host12')) + + 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_host_config_with_dns_opt(self): + + tested_opts = ['use-vc', 'no-tld-query'] + config = create_host_config(version='1.21', dns_opt=tested_opts) + dns_opts = config.get('DnsOptions') + + self.assertTrue('use-vc' in dns_opts) + self.assertTrue('no-tld-query' in dns_opts) + + self.assertRaises( + InvalidVersion, lambda: create_host_config(version='1.20', + dns_opt=tested_opts)) + + def test_create_host_config_with_mem_reservation(self): + config = create_host_config(version='1.21', mem_reservation=67108864) + self.assertEqual(config.get('MemoryReservation'), 67108864) + self.assertRaises( + InvalidVersion, lambda: create_host_config( + version='1.20', mem_reservation=67108864)) + + def test_create_host_config_with_kernel_memory(self): + config = create_host_config(version='1.21', kernel_memory=67108864) + self.assertEqual(config.get('KernelMemory'), 67108864) + self.assertRaises( + InvalidVersion, lambda: create_host_config( + version='1.20', kernel_memory=67108864)) + + def test_create_host_config_with_pids_limit(self): + config = create_host_config(version='1.23', pids_limit=1024) + self.assertEqual(config.get('PidsLimit'), 1024) + + with pytest.raises(InvalidVersion): + create_host_config(version='1.22', pids_limit=1024) + with pytest.raises(TypeError): + create_host_config(version='1.23', pids_limit='1024') + + def test_create_host_config_with_isolation(self): + config = create_host_config(version='1.24', isolation='hyperv') + self.assertEqual(config.get('Isolation'), 'hyperv') + + with pytest.raises(InvalidVersion): + create_host_config(version='1.23', isolation='hyperv') + with pytest.raises(TypeError): + create_host_config( + version='1.24', isolation={'isolation': 'hyperv'} + ) + + def test_create_host_config_pid_mode(self): + with pytest.raises(ValueError): + create_host_config(version='1.23', pid_mode='baccab125') + + config = create_host_config(version='1.23', pid_mode='host') + assert config.get('PidMode') == 'host' + config = create_host_config(version='1.24', pid_mode='baccab125') + assert config.get('PidMode') == 'baccab125' + + def test_create_host_config_invalid_mem_swappiness(self): + with pytest.raises(TypeError): + create_host_config(version='1.24', mem_swappiness='40') + + +class UlimitTest(unittest.TestCase): + 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(unittest.TestCase): + 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 EndpointConfigTest(unittest.TestCase): + def test_create_endpoint_config_with_aliases(self): + config = EndpointConfig(version='1.22', aliases=['foo', 'bar']) + assert config == {'Aliases': ['foo', 'bar']} + + with pytest.raises(InvalidVersion): + EndpointConfig(version='1.21', aliases=['foo', 'bar']) + + +class IPAMConfigTest(unittest.TestCase): + def test_create_ipam_config(self): + ipam_pool = IPAMPool(subnet='192.168.52.0/24', + gateway='192.168.52.254') + + ipam_config = IPAMConfig(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, + }] + }) diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index f69c62c..743d076 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -14,19 +14,17 @@ import pytest import six from docker.api.client import APIClient -from docker.constants import DEFAULT_DOCKER_API_VERSION, IS_WINDOWS_PLATFORM -from docker.errors import DockerException, InvalidVersion +from docker.constants import IS_WINDOWS_PLATFORM +from docker.errors import DockerException 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, - update_headers + parse_bytes, parse_env_file, exclude_paths, convert_volume_binds, + decode_json_header, tar, split_command, parse_devices, update_headers, ) from docker.utils.ports import build_port_bindings, split_port from docker.utils.utils import ( - create_endpoint_config, format_environment, should_check_directory + format_environment, should_check_directory ) from ..helpers import make_tree @@ -69,227 +67,6 @@ class DecoratorsTest(unittest.TestCase): } -class HostConfigTest(unittest.TestCase): - 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_blkio_constraints(self): - blkio_rate = [{"Path": "/dev/sda", "Rate": 1000}] - config = create_host_config(version='1.22', - blkio_weight=1999, - blkio_weight_device=blkio_rate, - device_read_bps=blkio_rate, - device_write_bps=blkio_rate, - device_read_iops=blkio_rate, - device_write_iops=blkio_rate) - - self.assertEqual(config.get('BlkioWeight'), 1999) - self.assertTrue(config.get('BlkioWeightDevice') is blkio_rate) - self.assertTrue(config.get('BlkioDeviceReadBps') is blkio_rate) - self.assertTrue(config.get('BlkioDeviceWriteBps') is blkio_rate) - self.assertTrue(config.get('BlkioDeviceReadIOps') is blkio_rate) - self.assertTrue(config.get('BlkioDeviceWriteIOps') is blkio_rate) - self.assertEqual(blkio_rate[0]['Path'], "/dev/sda") - self.assertEqual(blkio_rate[0]['Rate'], 1000) - - 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_userns_mode(self): - config = create_host_config(version='1.23', userns_mode='host') - self.assertEqual(config.get('UsernsMode'), 'host') - self.assertRaises( - InvalidVersion, lambda: create_host_config(version='1.22', - userns_mode='host')) - self.assertRaises( - ValueError, lambda: create_host_config(version='1.23', - userns_mode='host12')) - - 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_host_config_with_dns_opt(self): - - tested_opts = ['use-vc', 'no-tld-query'] - config = create_host_config(version='1.21', dns_opt=tested_opts) - dns_opts = config.get('DnsOptions') - - self.assertTrue('use-vc' in dns_opts) - self.assertTrue('no-tld-query' in dns_opts) - - self.assertRaises( - InvalidVersion, lambda: create_host_config(version='1.20', - dns_opt=tested_opts)) - - 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']) - - def test_create_host_config_with_mem_reservation(self): - config = create_host_config(version='1.21', mem_reservation=67108864) - self.assertEqual(config.get('MemoryReservation'), 67108864) - self.assertRaises( - InvalidVersion, lambda: create_host_config( - version='1.20', mem_reservation=67108864)) - - def test_create_host_config_with_kernel_memory(self): - config = create_host_config(version='1.21', kernel_memory=67108864) - self.assertEqual(config.get('KernelMemory'), 67108864) - self.assertRaises( - InvalidVersion, lambda: create_host_config( - version='1.20', kernel_memory=67108864)) - - def test_create_host_config_with_pids_limit(self): - config = create_host_config(version='1.23', pids_limit=1024) - self.assertEqual(config.get('PidsLimit'), 1024) - - with pytest.raises(InvalidVersion): - create_host_config(version='1.22', pids_limit=1024) - with pytest.raises(TypeError): - create_host_config(version='1.23', pids_limit='1024') - - def test_create_host_config_with_isolation(self): - config = create_host_config(version='1.24', isolation='hyperv') - self.assertEqual(config.get('Isolation'), 'hyperv') - - with pytest.raises(InvalidVersion): - create_host_config(version='1.23', isolation='hyperv') - with pytest.raises(TypeError): - create_host_config( - version='1.24', isolation={'isolation': 'hyperv'} - ) - - def test_create_host_config_pid_mode(self): - with pytest.raises(ValueError): - create_host_config(version='1.23', pid_mode='baccab125') - - config = create_host_config(version='1.23', pid_mode='host') - assert config.get('PidMode') == 'host' - config = create_host_config(version='1.24', pid_mode='baccab125') - assert config.get('PidMode') == 'baccab125' - - def test_create_host_config_invalid_mem_swappiness(self): - with pytest.raises(TypeError): - create_host_config(version='1.24', mem_swappiness='40') - - -class UlimitTest(unittest.TestCase): - 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(unittest.TestCase): - 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(unittest.TestCase): def setUp(self): self.os_environ = os.environ.copy() @@ -711,21 +488,6 @@ class UtilsTest(unittest.TestCase): 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(unittest.TestCase): def test_split_command_with_unicode(self): |