summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoffrey F <joffrey@docker.com>2017-10-25 19:03:12 -0700
committerJoffrey F <joffrey@docker.com>2017-10-26 14:13:04 -0700
commit9eff7a63f7212bd827d3ff24cb800caea13cc22f (patch)
tree84ab28b5002ebe049c752fdf850a6c5264e3a454
parent10ea65f5ab34cc9924e09ee9ed54d7005d997c40 (diff)
downloaddocker-py-9eff7a63f7212bd827d3ff24cb800caea13cc22f.tar.gz
Add support for new ContainerSpec parameters
Signed-off-by: Joffrey F <joffrey@docker.com>
-rw-r--r--docker/api/build.py7
-rw-r--r--docker/api/service.py65
-rw-r--r--docker/models/services.py37
-rw-r--r--docker/types/__init__.py5
-rw-r--r--docker/types/containers.py9
-rw-r--r--docker/types/healthcheck.py24
-rw-r--r--docker/types/services.py152
-rw-r--r--docker/utils/__init__.py2
-rw-r--r--docker/utils/utils.py6
-rw-r--r--docs/api.rst8
10 files changed, 265 insertions, 50 deletions
diff --git a/docker/api/build.py b/docker/api/build.py
index 42a1a29..25f271a 100644
--- a/docker/api/build.py
+++ b/docker/api/build.py
@@ -237,10 +237,9 @@ class BuildApiMixin(object):
'extra_hosts was only introduced in API version 1.27'
)
- encoded_extra_hosts = [
- '{}:{}'.format(k, v) for k, v in extra_hosts.items()
- ]
- params.update({'extrahosts': encoded_extra_hosts})
+ if isinstance(extra_hosts, dict):
+ extra_hosts = utils.format_extra_hosts(extra_hosts)
+ params.update({'extrahosts': extra_hosts})
if context is not None:
headers = {'Content-Type': 'application/tar'}
diff --git a/docker/api/service.py b/docker/api/service.py
index 4b555a5..9ce830c 100644
--- a/docker/api/service.py
+++ b/docker/api/service.py
@@ -4,45 +4,62 @@ from ..types import ServiceMode
def _check_api_features(version, task_template, update_config):
+
+ def raise_version_error(param, min_version):
+ raise errors.InvalidVersion(
+ '{} is not supported in API version < {}'.format(
+ param, min_version
+ )
+ )
+
if update_config is not None:
if utils.version_lt(version, '1.25'):
if 'MaxFailureRatio' in update_config:
- raise errors.InvalidVersion(
- 'UpdateConfig.max_failure_ratio is not supported in'
- ' API version < 1.25'
- )
+ raise_version_error('UpdateConfig.max_failure_ratio', '1.25')
if 'Monitor' in update_config:
- raise errors.InvalidVersion(
- 'UpdateConfig.monitor is not supported in'
- ' API version < 1.25'
- )
+ raise_version_error('UpdateConfig.monitor', '1.25')
if task_template is not None:
if 'ForceUpdate' in task_template and utils.version_lt(
version, '1.25'):
- raise errors.InvalidVersion(
- 'force_update is not supported in API version < 1.25'
- )
+ raise_version_error('force_update', '1.25')
if task_template.get('Placement'):
if utils.version_lt(version, '1.30'):
if task_template['Placement'].get('Platforms'):
- raise errors.InvalidVersion(
- 'Placement.platforms is not supported in'
- ' API version < 1.30'
- )
-
+ raise_version_error('Placement.platforms', '1.30')
if utils.version_lt(version, '1.27'):
if task_template['Placement'].get('Preferences'):
- raise errors.InvalidVersion(
- 'Placement.preferences is not supported in'
- ' API version < 1.27'
- )
- if task_template.get('ContainerSpec', {}).get('TTY'):
+ raise_version_error('Placement.preferences', '1.27')
+
+ if task_template.get('ContainerSpec'):
+ container_spec = task_template.get('ContainerSpec')
+
if utils.version_lt(version, '1.25'):
- raise errors.InvalidVersion(
- 'ContainerSpec.TTY is not supported in API version < 1.25'
- )
+ if container_spec.get('TTY'):
+ raise_version_error('ContainerSpec.tty', '1.25')
+ if container_spec.get('Hostname') is not None:
+ raise_version_error('ContainerSpec.hostname', '1.25')
+ if container_spec.get('Hosts') is not None:
+ raise_version_error('ContainerSpec.hosts', '1.25')
+ if container_spec.get('Groups') is not None:
+ raise_version_error('ContainerSpec.groups', '1.25')
+ if container_spec.get('DNSConfig') is not None:
+ raise_version_error('ContainerSpec.dns_config', '1.25')
+ if container_spec.get('Healthcheck') is not None:
+ raise_version_error('ContainerSpec.healthcheck', '1.25')
+
+ if utils.version_lt(version, '1.28'):
+ if container_spec.get('ReadOnly') is not None:
+ raise_version_error('ContainerSpec.dns_config', '1.28')
+ if container_spec.get('StopSignal') is not None:
+ raise_version_error('ContainerSpec.stop_signal', '1.28')
+
+ if utils.version_lt(version, '1.30'):
+ if container_spec.get('Configs') is not None:
+ raise_version_error('ContainerSpec.configs', '1.30')
+ if container_spec.get('Privileges') is not None:
+ raise_version_error('ContainerSpec.privileges', '1.30')
class ServiceApiMixin(object):
diff --git a/docker/models/services.py b/docker/models/services.py
index e1e2ea6..d45621b 100644
--- a/docker/models/services.py
+++ b/docker/models/services.py
@@ -147,6 +147,22 @@ class ServiceCollection(Collection):
user (str): User to run commands as.
workdir (str): Working directory for commands to run.
tty (boolean): Whether a pseudo-TTY should be allocated.
+ groups (:py:class:`list`): A list of additional groups that the
+ container process will run as.
+ open_stdin (boolean): Open ``stdin``
+ read_only (boolean): Mount the container's root filesystem as read
+ only.
+ stop_signal (string): Set signal to stop the service's containers
+ healthcheck (Healthcheck): Healthcheck
+ configuration for this service.
+ hosts (:py:class:`dict`): A set of host to IP mappings to add to
+ the container's `hosts` file.
+ dns_config (DNSConfig): Specification for DNS
+ related configurations in resolver configuration file.
+ configs (:py:class:`list`): List of :py:class:`ConfigReference`
+ that will be exposed to the service.
+ privileges (Privileges): Security options for the service's
+ containers.
Returns:
(:py:class:`Service`) The created service.
@@ -202,18 +218,27 @@ class ServiceCollection(Collection):
# kwargs to copy straight over to ContainerSpec
CONTAINER_SPEC_KWARGS = [
- 'image',
- 'command',
'args',
+ 'command',
+ 'configs',
+ 'dns_config',
'env',
+ 'groups',
+ 'healthcheck',
'hostname',
- 'workdir',
- 'user',
+ 'hosts',
+ 'image',
'labels',
'mounts',
- 'stop_grace_period',
+ 'open_stdin',
+ 'privileges'
+ 'read_only',
'secrets',
- 'tty'
+ 'stop_grace_period',
+ 'stop_signal',
+ 'tty',
+ 'user',
+ 'workdir',
]
# kwargs to copy straight over to TaskTemplate
diff --git a/docker/types/__init__.py b/docker/types/__init__.py
index edc919d..39c93e3 100644
--- a/docker/types/__init__.py
+++ b/docker/types/__init__.py
@@ -3,7 +3,8 @@ 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, Placement, Resources,
- RestartPolicy, SecretReference, ServiceMode, TaskTemplate, UpdateConfig
+ ConfigReference, ContainerSpec, DNSConfig, DriverConfig, EndpointSpec,
+ Mount, Placement, Privileges, Resources, RestartPolicy, SecretReference,
+ ServiceMode, TaskTemplate, UpdateConfig
)
from .swarm import SwarmSpec, SwarmExternalCA
diff --git a/docker/types/containers.py b/docker/types/containers.py
index 3fc13d9..13bea71 100644
--- a/docker/types/containers.py
+++ b/docker/types/containers.py
@@ -4,8 +4,8 @@ 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,
+ format_environment, format_extra_hosts, normalize_links, parse_bytes,
+ parse_devices, split_command, version_gte, version_lt,
)
from .base import DictType
from .healthcheck import Healthcheck
@@ -257,10 +257,7 @@ class HostConfig(dict):
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))
- ]
+ extra_hosts = format_extra_hosts(extra_hosts)
self['ExtraHosts'] = extra_hosts
diff --git a/docker/types/healthcheck.py b/docker/types/healthcheck.py
index 8ea9a35..5a6a931 100644
--- a/docker/types/healthcheck.py
+++ b/docker/types/healthcheck.py
@@ -4,6 +4,30 @@ import six
class Healthcheck(DictType):
+ """
+ Defines a healthcheck configuration for a container or service.
+
+ Args:
+
+ test (:py:class:`list` or str): Test to perform to determine
+ container health. Possible values:
+ - Empty list: Inherit healthcheck from parent image
+ - ``["NONE"]``: Disable healthcheck
+ - ``["CMD", args...]``: exec arguments directly.
+ - ``["CMD-SHELL", command]``: RUn command in the system's
+ default shell.
+ If a string is provided, it will be used as a ``CMD-SHELL``
+ command.
+ interval (int): The time to wait between checks in nanoseconds. It
+ should be 0 or at least 1000000 (1 ms).
+ timeout (int): The time to wait before considering the check to
+ have hung. It should be 0 or at least 1000000 (1 ms).
+ retries (integer): The number of consecutive failures needed to
+ consider a container as unhealthy.
+ start_period (integer): Start period for the container to
+ initialize before starting health-retries countdown in
+ nanoseconds. It should be 0 or at least 1000000 (1 ms).
+ """
def __init__(self, **kwargs):
test = kwargs.get('test', kwargs.get('Test'))
if isinstance(test, six.string_types):
diff --git a/docker/types/services.py b/docker/types/services.py
index c276740..c77db16 100644
--- a/docker/types/services.py
+++ b/docker/types/services.py
@@ -3,7 +3,8 @@ import six
from .. import errors
from ..constants import IS_WINDOWS_PLATFORM
from ..utils import (
- check_resource, format_environment, parse_bytes, split_command
+ check_resource, format_environment, format_extra_hosts, parse_bytes,
+ split_command,
)
@@ -84,13 +85,31 @@ class ContainerSpec(dict):
: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.
- secrets (list of py:class:`SecretReference`): List of secrets to be
+ secrets (:py:class:`list`): List of :py:class:`SecretReference` to be
made available inside the containers.
tty (boolean): Whether a pseudo-TTY should be allocated.
+ groups (:py:class:`list`): A list of additional groups that the
+ container process will run as.
+ open_stdin (boolean): Open ``stdin``
+ read_only (boolean): Mount the container's root filesystem as read
+ only.
+ stop_signal (string): Set signal to stop the service's containers
+ healthcheck (Healthcheck): Healthcheck
+ configuration for this service.
+ hosts (:py:class:`dict`): A set of host to IP mappings to add to
+ the container's `hosts` file.
+ dns_config (DNSConfig): Specification for DNS
+ related configurations in resolver configuration file.
+ configs (:py:class:`list`): List of :py:class:`ConfigReference` that
+ will be exposed to the service.
+ privileges (Privileges): Security options for the service's containers.
"""
def __init__(self, image, command=None, args=None, hostname=None, env=None,
workdir=None, user=None, labels=None, mounts=None,
- stop_grace_period=None, secrets=None, tty=None):
+ stop_grace_period=None, secrets=None, tty=None, groups=None,
+ open_stdin=None, read_only=None, stop_signal=None,
+ healthcheck=None, hosts=None, dns_config=None, configs=None,
+ privileges=None):
self['Image'] = image
if isinstance(command, six.string_types):
@@ -109,8 +128,17 @@ class ContainerSpec(dict):
self['Dir'] = workdir
if user is not None:
self['User'] = user
+ if groups is not None:
+ self['Groups'] = groups
+ if stop_signal is not None:
+ self['StopSignal'] = stop_signal
+ if stop_grace_period is not None:
+ self['StopGracePeriod'] = stop_grace_period
if labels is not None:
self['Labels'] = labels
+ if hosts is not None:
+ self['Hosts'] = format_extra_hosts(hosts)
+
if mounts is not None:
parsed_mounts = []
for mount in mounts:
@@ -120,16 +148,30 @@ class ContainerSpec(dict):
# If mount already parsed
parsed_mounts.append(mount)
self['Mounts'] = parsed_mounts
- if stop_grace_period is not None:
- self['StopGracePeriod'] = stop_grace_period
if secrets is not None:
if not isinstance(secrets, list):
raise TypeError('secrets must be a list')
self['Secrets'] = secrets
+ if configs is not None:
+ if not isinstance(configs, list):
+ raise TypeError('configs must be a list')
+ self['Configs'] = configs
+
+ if dns_config is not None:
+ self['DNSConfig'] = dns_config
+ if privileges is not None:
+ self['Privileges'] = privileges
+ if healthcheck is not None:
+ self['Healthcheck'] = healthcheck
+
if tty is not None:
self['TTY'] = tty
+ if open_stdin is not None:
+ self['OpenStdin'] = open_stdin
+ if read_only is not None:
+ self['ReadOnly'] = read_only
class Mount(dict):
@@ -487,6 +529,34 @@ class SecretReference(dict):
}
+class ConfigReference(dict):
+ """
+ Config reference to be used as part of a :py:class:`ContainerSpec`.
+ Describes how a config is made accessible inside the service's
+ containers.
+
+ Args:
+ config_id (string): Config's ID
+ config_name (string): Config's name as defined at its creation.
+ filename (string): Name of the file containing the config. Defaults
+ to the config's name if not specified.
+ uid (string): UID of the config file's owner. Default: 0
+ gid (string): GID of the config file's group. Default: 0
+ mode (int): File access mode inside the container. Default: 0o444
+ """
+ @check_resource('config_id')
+ def __init__(self, config_id, config_name, filename=None, uid=None,
+ gid=None, mode=0o444):
+ self['ConfigName'] = config_name
+ self['ConfigID'] = config_id
+ self['File'] = {
+ 'Name': filename or config_name,
+ 'UID': uid or '0',
+ 'GID': gid or '0',
+ 'Mode': mode
+ }
+
+
class Placement(dict):
"""
Placement constraints to be used as part of a :py:class:`TaskTemplate`
@@ -510,3 +580,75 @@ class Placement(dict):
self['Platforms'].append({
'Architecture': plat[0], 'OS': plat[1]
})
+
+
+class DNSConfig(dict):
+ """
+ Specification for DNS related configurations in resolver configuration
+ file (``resolv.conf``). Part of a :py:class:`ContainerSpec` definition.
+
+ Args:
+ nameservers (:py:class:`list`): The IP addresses of the name
+ servers.
+ search (:py:class:`list`): A search list for host-name lookup.
+ options (:py:class:`list`): A list of internal resolver variables
+ to be modified (e.g., ``debug``, ``ndots:3``, etc.).
+ """
+ def __init__(self, nameservers=None, search=None, options=None):
+ self['Nameservers'] = nameservers
+ self['Search'] = search
+ self['Options'] = options
+
+
+class Privileges(dict):
+ """
+ Security options for a service's containers.
+ Part of a :py:class:`ContainerSpec` definition.
+
+ Args:
+ credentialspec_file (str): Load credential spec from this file.
+ The file is read by the daemon, and must be present in the
+ CredentialSpecs subdirectory in the docker data directory,
+ which defaults to ``C:\ProgramData\Docker\`` on Windows.
+ Can not be combined with credentialspec_registry.
+
+ credentialspec_registry (str): Load credential spec from this value
+ in the Windows registry. The specified registry value must be
+ located in: ``HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion
+ \Virtualization\Containers\CredentialSpecs``.
+ Can not be combined with credentialspec_file.
+
+ selinux_disable (boolean): Disable SELinux
+ selinux_user (string): SELinux user label
+ selinux_role (string): SELinux role label
+ selinux_type (string): SELinux type label
+ selinux_level (string): SELinux level label
+ """
+ def __init__(self, credentialspec_file=None, credentialspec_registry=None,
+ selinux_disable=None, selinux_user=None, selinux_role=None,
+ selinux_type=None, selinux_level=None):
+ credential_spec = {}
+ if credentialspec_registry is not None:
+ credential_spec['Registry'] = credentialspec_registry
+ if credentialspec_file is not None:
+ credential_spec['File'] = credentialspec_file
+
+ if len(credential_spec) > 1:
+ raise errors.InvalidArgument(
+ 'credentialspec_file and credentialspec_registry are mutually'
+ ' exclusive'
+ )
+
+ selinux_context = {
+ 'Disable': selinux_disable,
+ 'User': selinux_user,
+ 'Role': selinux_role,
+ 'Type': selinux_type,
+ 'Level': selinux_level,
+ }
+
+ if len(credential_spec) > 0:
+ self['CredentialSpec'] = credential_spec
+
+ if len(selinux_context) > 0:
+ self['SELinuxContext'] = selinux_context
diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py
index b758cbd..c162e3b 100644
--- a/docker/utils/__init__.py
+++ b/docker/utils/__init__.py
@@ -8,6 +8,6 @@ from .utils import (
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,
- format_environment, create_archive
+ format_environment, create_archive, format_extra_hosts
)
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
index d9a6d7c..a123fd8 100644
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -564,6 +564,12 @@ def format_environment(environment):
return [format_env(*var) for var in six.iteritems(environment)]
+def format_extra_hosts(extra_hosts):
+ return [
+ '{}:{}'.format(k, v) for k, v in sorted(six.iteritems(extra_hosts))
+ ]
+
+
def create_host_config(self, *args, **kwargs):
raise errors.DeprecatedMethod(
'utils.create_host_config has been removed. Please use a '
diff --git a/docs/api.rst b/docs/api.rst
index 0b10f38..2fce0a7 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -122,13 +122,17 @@ Configuration types
.. py:module:: docker.types
-.. autoclass:: IPAMConfig
-.. autoclass:: IPAMPool
+.. autoclass:: ConfigReference
.. autoclass:: ContainerSpec
+.. autoclass:: DNSConfig
.. autoclass:: DriverConfig
.. autoclass:: EndpointSpec
+.. autoclass:: Healthcheck
+.. autoclass:: IPAMConfig
+.. autoclass:: IPAMPool
.. autoclass:: Mount
.. autoclass:: Placement
+.. autoclass:: Privileges
.. autoclass:: Resources
.. autoclass:: RestartPolicy
.. autoclass:: SecretReference