Merge pull request #27676 from ticosax/no-more-arg-to-docker-start

[dockerng] WIP No more runtime args passed to docker.start()
This commit is contained in:
Erik Johnson 2015-10-14 08:38:41 -05:00
commit 2d0b16559e
3 changed files with 428 additions and 508 deletions

View file

@ -289,7 +289,7 @@ __func_alias__ = {
}
# Minimum supported versions
MIN_DOCKER = (1, 0, 0)
MIN_DOCKER = (1, 4, 0)
MIN_DOCKER_PY = (1, 4, 0)
VERSION_RE = r'([\d.]+)'
@ -420,9 +420,6 @@ VALID_CREATE_OPTS = {
'labels': {
'path': 'Config:Labels',
},
}
VALID_RUNTIME_OPTS = {
'binds': {
'path': 'HostConfig:Binds',
},
@ -525,18 +522,7 @@ def _get_docker_py_versioninfo():
'''
Returns a version_info tuple for docker-py
'''
contextkey = 'docker.docker_py_version'
if contextkey in __context__:
return __context__[contextkey]
match = re.match(VERSION_RE, str(docker.__version__))
if match:
__context__[contextkey] = tuple(
[int(x) for x in match.group(1).split('.')]
)
else:
log.warning('Unable to determine docker-py version')
__context__[contextkey] = None
return __context__[contextkey]
return docker.version_info
# Decorators
@ -1050,8 +1036,7 @@ def _error_detail(data, item):
data.append(msg)
def _validate_input(action,
kwargs,
def _validate_input(kwargs,
validate_ip_addrs=True):
'''
Perform validation on kwargs. Checks each key in kwargs against the
@ -1681,15 +1666,6 @@ def _validate_input(action,
kwargs['labels'] = salt.utils.repack_dictlist(kwargs['labels'])
# And now, the actual logic to perform the validation
if action == 'create':
valid_opts = VALID_CREATE_OPTS
elif action == 'runtime':
valid_opts = VALID_RUNTIME_OPTS
else:
raise SaltInvocationError(
'Invalid validation action \'{0}\''.format(action)
)
if 'docker.docker_version' not in __context__:
# Have to call this func using the __salt__ dunder (instead of just
# version()) because this _validate_input() will be imported into the
@ -1707,40 +1683,40 @@ def _validate_input(action,
_locals = locals()
for kwarg in kwargs:
if kwarg not in valid_opts:
if kwarg not in VALID_CREATE_OPTS:
raise SaltInvocationError('Invalid argument \'{0}\''.format(kwarg))
# Check for Docker/docker-py compatibility
compat_errors = []
if 'min_docker' in valid_opts[kwarg]:
min_docker = valid_opts[kwarg]['min_docker']
if 'min_docker' in VALID_CREATE_OPTS[kwarg]:
min_docker = VALID_CREATE_OPTS[kwarg]['min_docker']
if __context__['docker.docker_version'] is not None:
if __context__['docker.docker_version'] < min_docker:
compat_errors.append(
'The \'{0}\' parameter requires at least Docker {1} '
'(detected version {2})'.format(
kwarg,
'.'.join(min_docker),
'.'.join(map(str, min_docker)),
'.'.join(__context__['docker.docker_version'])
)
)
if 'min_docker_py' in valid_opts[kwarg]:
if 'min_docker_py' in VALID_CREATE_OPTS[kwarg]:
cur_docker_py = _get_docker_py_versioninfo()
if cur_docker_py is not None:
min_docker_py = valid_opts[kwarg]['min_docker_py']
min_docker_py = VALID_CREATE_OPTS[kwarg]['min_docker_py']
if cur_docker_py < min_docker_py:
compat_errors.append(
'The \'{0}\' parameter requires at least docker-py '
'{1} (detected version {2})'.format(
kwarg,
'.'.join(min_docker_py),
'.'.join(cur_docker_py)
'.'.join(map(str, min_docker_py)),
'.'.join(map(str, cur_docker_py))
)
)
if compat_errors:
raise SaltInvocationError('; '.join(compat_errors))
default_val = valid_opts[kwarg].get('default')
default_val = VALID_CREATE_OPTS[kwarg].get('default')
if kwargs[kwarg] is None:
if default_val is None:
# Passed as None and None is the default. Skip validation. This
@ -1751,7 +1727,7 @@ def _validate_input(action,
# None, don't let them do this.
raise SaltInvocationError(kwarg + ' cannot be None')
validator = valid_opts[kwarg].get('validator')
validator = VALID_CREATE_OPTS[kwarg].get('validator')
if validator is None:
# Look for custom validation function
validator = kwarg
@ -2549,6 +2525,7 @@ def version():
@_refresh_mine_cache
def create(image,
name=None,
validate_ip_addrs=True,
client_timeout=CLIENT_TIMEOUT,
**kwargs):
'''
@ -2689,6 +2666,166 @@ def create(image,
Example: ``labels=LABEL1,LABEL2``,
``labels="{'LABEL1': 'value1', 'LABEL2': 'value2'}"``
validate_ip_addrs : True
For parameters which accept IP addresses as input, IP address
validation will be performed. To disable, set this to ``False``
binds
Files/directories to bind mount. Each bind mount should be passed in
the format ``<host_path>:<container_path>:<read_only>``, where
``<read_only>`` is one of ``rw`` (for read-write access) or ``ro`` (for
read-only access). Optionally, the read-only information can be left
off the end and the bind mount will be assumed to be read-write.
Examples 2 and 3 below are equivalent.
Example 1: ``binds=/srv/www:/var/www:ro``
Example 2: ``binds=/srv/www:/var/www:rw``
Example 3: ``binds=/srv/www:/var/www``
port_bindings
Bind exposed ports which were exposed using the ``ports`` argument to
:py:func:`dockerng.create <salt.modules.dockerng.create>`. These
should be passed in the same way as the ``--publish`` argument to the
``docker run`` CLI command:
- ``ip:hostPort:containerPort`` - Bind a specific IP and port on the
host to a specific port within the container.
- ``ip::containerPort`` - Bind a specific IP and an ephemeral port to a
specific port within the container.
- ``hostPort:containerPort`` - Bind a specific port on all of the
host's interfaces to a specific port within the container.
- ``containerPort`` - Bind an ephemeral port on all of the host's
interfaces to a specific port within the container.
Multiple bindings can be separated by commas, or passed as a Python
list. The below two examples are equivalent:
Example 1: ``port_bindings="5000:5000,2123:2123/udp,8080"``
Example 2: ``port_bindings="['5000:5000', '2123:2123/udp', '8080']"``
.. note::
When configuring bindings for UDP ports, the protocol must be
passed in the ``containerPort`` value, as seen in the examples
above.
lxc_conf
Additional LXC configuration parameters to set before starting the
container.
Example: ``lxc_conf="{lxc.utsname: docker}"``
.. note::
These LXC configuration parameters will only have the desired
effect if the container is using the LXC execution driver, which
has not been the default for some time.
publish_all_ports : False
Allocates a random host port for each port exposed using the ``ports``
argument to :py:func:`dockerng.create <salt.modules.dockerng.create>`.
Example: ``publish_all_ports=True``
links
Link this container to another. Links should be specified in the format
``<container_name_or_id>:<link_alias>``. Multiple links can be passed,
ether as a comma separated list or a Python list.
Example 1: ``links=mycontainer:myalias``,
``links=web1:link1,web2:link2``
Example 2: ``links="['mycontainer:myalias']"``
``links="['web1:link1', 'web2:link2']"``
dns
List of DNS nameservers. Can be passed as a comma-separated list or a
Python list.
Example: ``dns=8.8.8.8,8.8.4.4`` or ``dns="[8.8.8.8, 8.8.4.4]"``
.. note::
To skip IP address validation, use ``validate_ip_addrs=False``
dns_search
List of DNS search domains. Can be passed as a comma-separated list
or a Python list.
Example: ``dns_search=foo1.domain.tld,foo2.domain.tld`` or
``dns_search="[foo1.domain.tld, foo2.domain.tld]"``
volumes_from
Container names or IDs from which the container will get volumes. Can
be passed as a comma-separated list or a Python list.
Example: ``volumes_from=foo``, ``volumes_from=foo,bar``,
``volumes_from="[foo, bar]"``
network_mode : bridge
One of the following:
- ``bridge`` - Creates a new network stack for the container on the
docker bridge
- ``null`` - No networking (equivalent of the Docker CLI argument
``--net=none``)
- ``container:<name_or_id>`` - Reuses another container's network stack
- ``host`` - Use the host's network stack inside the container
.. warning::
Using ``host`` mode gives the container full access to the
hosts system's services (such as D-bus), and is therefore
considered insecure.
Example: ``network_mode=null``, ``network_mode=container:web1``
restart_policy
Set a restart policy for the container. Must be passed as a string in
the format ``policy[:retry_count]`` where ``policy`` is one of
``always`` or ``on-failure``, and ``retry_count`` is an optional limit
to the number of retries. The retry count is ignored when using the
``always`` restart policy.
Example 1: ``restart_policy=on-failure:5``
Example 2: ``restart_policy=always``
cap_add
List of capabilities to add within the container. Can be passed as a
comma-separated list or a Python list. Requires Docker 1.2.0 or
newer.
Example: ``cap_add=SYS_ADMIN,MKNOD``, ``cap_add="[SYS_ADMIN, MKNOD]"``
cap_drop
List of capabilities to drop within the container. Can be passed as a
comma-separated string or a Python list. Requires Docker 1.2.0 or
newer.
Example: ``cap_drop=SYS_ADMIN,MKNOD``,
``cap_drop="[SYS_ADMIN, MKNOD]"``
extra_hosts
Additional hosts to add to the container's /etc/hosts file. Can be
passed as a comma-separated list or a Python list. Requires Docker
1.3.0 or newer.
Example: ``extra_hosts=web1:10.9.8.7,web2:10.9.8.8``
.. note::
To skip IP address validation, use ``validate_ip_addrs=False``
pid_mode
Set to ``host`` to use the host container's PID namespace within the
container. Requires Docker 1.5.0 or newer.
Example: ``pid_mode=host``
**RETURN DATA**
A dictionary containing the following keys:
@ -2727,7 +2864,7 @@ def create(image,
create_kwargs['hostname'] = create_kwargs['name']
if create_kwargs.pop('validate_input', False):
_validate_input('create', create_kwargs)
_validate_input(create_kwargs, validate_ip_addrs=validate_ip_addrs)
# Rename the kwargs whose names differ from their counterparts in the
# docker.client.Client class member functions. Can't use iterators here
@ -4226,174 +4363,13 @@ def signal_(name, signal):
@_refresh_mine_cache
@_ensure_exists
def start(name, validate_ip_addrs=True, **kwargs):
def start(name):
'''
Start a container
name
Container name or ID
validate_ip_addrs : True
For parameters which accept IP addresses as input, IP address
validation will be performed. To disable, set this to ``False``
binds
Files/directories to bind mount. Each bind mount should be passed in
the format ``<host_path>:<container_path>:<read_only>``, where
``<read_only>`` is one of ``rw`` (for read-write access) or ``ro`` (for
read-only access). Optionally, the read-only information can be left
off the end and the bind mount will be assumed to be read-write.
Examples 2 and 3 below are equivalent.
Example 1: ``binds=/srv/www:/var/www:ro``
Example 2: ``binds=/srv/www:/var/www:rw``
Example 3: ``binds=/srv/www:/var/www``
port_bindings
Bind exposed ports which were exposed using the ``ports`` argument to
:py:func:`dockerng.create <salt.modules.dockerng.create>`. These
should be passed in the same way as the ``--publish`` argument to the
``docker run`` CLI command:
- ``ip:hostPort:containerPort`` - Bind a specific IP and port on the
host to a specific port within the container.
- ``ip::containerPort`` - Bind a specific IP and an ephemeral port to a
specific port within the container.
- ``hostPort:containerPort`` - Bind a specific port on all of the
host's interfaces to a specific port within the container.
- ``containerPort`` - Bind an ephemeral port on all of the host's
interfaces to a specific port within the container.
Multiple bindings can be separated by commas, or passed as a Python
list. The below two examples are equivalent:
Example 1: ``port_bindings="5000:5000,2123:2123/udp,8080"``
Example 2: ``port_bindings="['5000:5000', '2123:2123/udp', '8080']"``
.. note::
When configuring bindings for UDP ports, the protocol must be
passed in the ``containerPort`` value, as seen in the examples
above.
lxc_conf
Additional LXC configuration parameters to set before starting the
container.
Example: ``lxc_conf="{lxc.utsname: docker}"``
.. note::
These LXC configuration parameters will only have the desired
effect if the container is using the LXC execution driver, which
has not been the default for some time.
publish_all_ports : False
Allocates a random host port for each port exposed using the ``ports``
argument to :py:func:`dockerng.create <salt.modules.dockerng.create>`.
Example: ``publish_all_ports=True``
links
Link this container to another. Links should be specified in the format
``<container_name_or_id>:<link_alias>``. Multiple links can be passed,
ether as a comma separated list or a Python list.
Example 1: ``links=mycontainer:myalias``,
``links=web1:link1,web2:link2``
Example 2: ``links="['mycontainer:myalias']"``
``links="['web1:link1', 'web2:link2']"``
dns
List of DNS nameservers. Can be passed as a comma-separated list or a
Python list.
Example: ``dns=8.8.8.8,8.8.4.4`` or ``dns="[8.8.8.8, 8.8.4.4]"``
.. note::
To skip IP address validation, use ``validate_ip_addrs=False``
dns_search
List of DNS search domains. Can be passed as a comma-separated list
or a Python list.
Example: ``dns_search=foo1.domain.tld,foo2.domain.tld`` or
``dns_search="[foo1.domain.tld, foo2.domain.tld]"``
volumes_from
Container names or IDs from which the container will get volumes. Can
be passed as a comma-separated list or a Python list.
Example: ``volumes_from=foo``, ``volumes_from=foo,bar``,
``volumes_from="[foo, bar]"``
network_mode : bridge
One of the following:
- ``bridge`` - Creates a new network stack for the container on the
docker bridge
- ``null`` - No networking (equivalent of the Docker CLI argument
``--net=none``)
- ``container:<name_or_id>`` - Reuses another container's network stack
- ``host`` - Use the host's network stack inside the container
.. warning::
Using ``host`` mode gives the container full access to the
hosts system's services (such as D-bus), and is therefore
considered insecure.
Example: ``network_mode=null``, ``network_mode=container:web1``
restart_policy
Set a restart policy for the container. Must be passed as a string in
the format ``policy[:retry_count]`` where ``policy`` is one of
``always`` or ``on-failure``, and ``retry_count`` is an optional limit
to the number of retries. The retry count is ignored when using the
``always`` restart policy.
Example 1: ``restart_policy=on-failure:5``
Example 2: ``restart_policy=always``
cap_add
List of capabilities to add within the container. Can be passed as a
comma-separated list or a Python list. Requires Docker 1.2.0 or
newer.
Example: ``cap_add=SYS_ADMIN,MKNOD``, ``cap_add="[SYS_ADMIN, MKNOD]"``
cap_drop
List of capabilities to drop within the container. Can be passed as a
comma-separated string or a Python list. Requires Docker 1.2.0 or
newer.
Example: ``cap_drop=SYS_ADMIN,MKNOD``,
``cap_drop="[SYS_ADMIN, MKNOD]"``
extra_hosts
Additional hosts to add to the container's /etc/hosts file. Can be
passed as a comma-separated list or a Python list. Requires Docker
1.3.0 or newer.
Example: ``extra_hosts=web1:10.9.8.7,web2:10.9.8.8``
.. note::
To skip IP address validation, use ``validate_ip_addrs=False``
pid_mode
Set to ``host`` to use the host container's PID namespace within the
container. Requires Docker 1.5.0 or newer.
Example: ``pid_mode=host``
**RETURN DATA**
A dictionary will be returned, containing the following keys:
@ -4417,26 +4393,7 @@ def start(name, validate_ip_addrs=True, **kwargs):
'comment': ('Container \'{0}\' is paused, cannot start'
.format(name))}
runtime_kwargs = salt.utils.clean_kwargs(**copy.deepcopy(kwargs))
if runtime_kwargs.pop('validate_input', False):
_validate_input('runtime',
runtime_kwargs,
validate_ip_addrs=validate_ip_addrs)
# Rename the kwargs whose names differ from their counterparts in the
# docker.client.Client class member functions. Can't use iterators here
# because we're going to be modifying the dict.
for key in list(six.iterkeys(VALID_RUNTIME_OPTS)):
if key in runtime_kwargs:
val = VALID_RUNTIME_OPTS[key]
if 'api_name' in val:
runtime_kwargs[val['api_name']] = runtime_kwargs.pop(key)
log.debug(
'dockerng.start is using the following kwargs to start container '
'\'{0}\': {1}'.format(name, runtime_kwargs)
)
return _change_state(name, 'start', 'running', **runtime_kwargs)
return _change_state(name, 'start', 'running')
@_refresh_mine_cache

View file

@ -49,7 +49,6 @@ from salt.modules.dockerng import (
CLIENT_TIMEOUT,
STOP_TIMEOUT,
VALID_CREATE_OPTS,
VALID_RUNTIME_OPTS,
_validate_input,
_get_repo_tag
)
@ -122,7 +121,7 @@ def _prep_input(kwargs):
raise SaltInvocationError(err)
def _compare(actual, create_kwargs, runtime_kwargs):
def _compare(actual, create_kwargs):
'''
Compare the desired configuration against the actual configuration returned
by dockerng.inspect_container
@ -131,250 +130,247 @@ def _compare(actual, create_kwargs, runtime_kwargs):
salt.utils.traverse_dict(actual, path, NOTSET, delimiter=':')
)
ret = {}
for desired, valid_opts in ((create_kwargs, VALID_CREATE_OPTS),
(runtime_kwargs, VALID_RUNTIME_OPTS)):
for item, data, in six.iteritems(desired):
if item not in valid_opts:
log.error(
'Trying to compare \'{0}\', but it is not a valid '
'parameter. Skipping.'.format(item)
)
continue
log.trace('dockerng.running: comparing ' + item)
conf_path = valid_opts[item]['path']
if isinstance(conf_path, tuple):
actual_data = [_get(x) for x in conf_path]
for val in actual_data:
if val is NOTSET:
_api_mismatch(item)
else:
actual_data = _get(conf_path)
if actual_data is NOTSET:
for item, data, in six.iteritems(create_kwargs):
if item not in VALID_CREATE_OPTS:
log.error(
'Trying to compare \'{0}\', but it is not a valid '
'parameter. Skipping.'.format(item)
)
continue
log.trace('dockerng.running: comparing ' + item)
conf_path = VALID_CREATE_OPTS[item]['path']
if isinstance(conf_path, tuple):
actual_data = [_get(x) for x in conf_path]
for val in actual_data:
if val is NOTSET:
_api_mismatch(item)
log.trace('dockerng.running ({0}): desired value: {1}'
.format(item, data))
log.trace('dockerng.running ({0}): actual value: {1}'
.format(item, actual_data))
else:
actual_data = _get(conf_path)
if actual_data is NOTSET:
_api_mismatch(item)
log.trace('dockerng.running ({0}): desired value: {1}'
.format(item, data))
log.trace('dockerng.running ({0}): actual value: {1}'
.format(item, actual_data))
if actual_data is None and data is not None \
or actual_data is not None and data is None:
ret.update({item: {'old': actual_data, 'new': data}})
continue
if actual_data is None and data is not None \
or actual_data is not None and data is None:
ret.update({item: {'old': actual_data, 'new': data}})
continue
# 'create' comparison params
if item == 'detach':
# Something unique here. Two fields to check, if both are False
# then detach is True
actual_detach = all(x is False for x in actual_data)
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_detach))
if actual_detach != data:
ret.update({item: {'old': actual_detach, 'new': data}})
continue
# 'create' comparison params
if item == 'detach':
# Something unique here. Two fields to check, if both are False
# then detach is True
actual_detach = all(x is False for x in actual_data)
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_detach))
if actual_detach != data:
ret.update({item: {'old': actual_detach, 'new': data}})
continue
elif item == 'environment':
actual_env = {}
for env_var in actual_data:
try:
key, val = env_var.split('=', 1)
except (AttributeError, ValueError):
log.warning(
'Unexpected environment variable in inspect '
'output {0}'.format(env_var)
)
continue
else:
actual_env[key] = val
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_env))
env_diff = {}
for key in data:
actual_val = actual_env.get(key)
if data[key] != actual_val:
env_ptr = env_diff.setdefault(item, {})
env_ptr.setdefault('old', {})[key] = actual_val
env_ptr.setdefault('new', {})[key] = data[key]
if env_diff:
ret.update(env_diff)
continue
elif item == 'ports':
# Munge the desired configuration instead of the actual
# configuration here, because the desired configuration is a
# list of ints or tuples, and that won't look as good in the
# nested outputter as a simple comparison of lists of
# port/protocol pairs (as found in the "actual" dict).
actual_ports = sorted(actual_data)
desired_ports = []
for port_def in data:
if isinstance(port_def, tuple):
desired_ports.append('{0}/{1}'.format(*port_def))
else:
desired_ports.append('{0}/tcp'.format(port_def))
desired_ports.sort()
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_ports))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_ports))
if actual_ports != desired_ports:
ret.update({item: {'old': actual_ports,
'new': desired_ports}})
continue
# 'runtime' comparison params
elif item == 'binds':
actual_binds = []
for bind in actual_data:
bind_parts = bind.split(':')
if len(bind_parts) == 2:
actual_binds.append(bind + ':rw')
else:
actual_binds.append(bind)
desired_binds = []
for host_path, bind_data in six.iteritems(data):
desired_binds.append(
'{0}:{1}:{2}'.format(
host_path,
bind_data['bind'],
'ro' if bind_data['ro'] else 'rw'
)
elif item == 'environment':
actual_env = {}
for env_var in actual_data:
try:
key, val = env_var.split('=', 1)
except (AttributeError, ValueError):
log.warning(
'Unexpected environment variable in inspect '
'output {0}'.format(env_var)
)
actual_binds.sort()
desired_binds.sort()
if actual_binds != desired_binds:
ret.update({item: {'old': actual_binds,
'new': desired_binds}})
continue
else:
actual_env[key] = val
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_env))
env_diff = {}
for key in data:
actual_val = actual_env.get(key)
if data[key] != actual_val:
env_ptr = env_diff.setdefault(item, {})
env_ptr.setdefault('old', {})[key] = actual_val
env_ptr.setdefault('new', {})[key] = data[key]
if env_diff:
ret.update(env_diff)
continue
elif item == 'port_bindings':
actual_binds = []
for container_port, bind_list in six.iteritems(actual_data):
elif item == 'ports':
# Munge the desired configuration instead of the actual
# configuration here, because the desired configuration is a
# list of ints or tuples, and that won't look as good in the
# nested outputter as a simple comparison of lists of
# port/protocol pairs (as found in the "actual" dict).
actual_ports = sorted(actual_data)
desired_ports = []
for port_def in data:
if isinstance(port_def, tuple):
desired_ports.append('{0}/{1}'.format(*port_def))
else:
desired_ports.append('{0}/tcp'.format(port_def))
desired_ports.sort()
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_ports))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_ports))
if actual_ports != desired_ports:
ret.update({item: {'old': actual_ports,
'new': desired_ports}})
continue
elif item == 'binds':
actual_binds = []
for bind in actual_data:
bind_parts = bind.split(':')
if len(bind_parts) == 2:
actual_binds.append(bind + ':rw')
else:
actual_binds.append(bind)
desired_binds = []
for host_path, bind_data in six.iteritems(data):
desired_binds.append(
'{0}:{1}:{2}'.format(
host_path,
bind_data['bind'],
'ro' if bind_data['ro'] else 'rw'
)
)
actual_binds.sort()
desired_binds.sort()
if actual_binds != desired_binds:
ret.update({item: {'old': actual_binds,
'new': desired_binds}})
continue
elif item == 'port_bindings':
actual_binds = []
for container_port, bind_list in six.iteritems(actual_data):
if container_port.endswith('/tcp'):
container_port = container_port[:-4]
for bind_data in bind_list:
# Port range will have to be updated for future Docker
# versions (see
# https://github.com/docker/docker/issues/10220). Note
# that Docker 1.5.0 (released a few weeks after the fix
# was merged) does not appear to have this fix in it,
# so we're probably looking at 1.6.0 for this fix.
if bind_data['HostPort'] == '' or \
49153 <= int(bind_data['HostPort']) <= 65535:
host_port = ''
else:
host_port = bind_data['HostPort']
if bind_data['HostIp'] in ('0.0.0.0', ''):
if host_port:
bind_def = (host_port, container_port)
else:
bind_def = (container_port,)
else:
bind_def = (bind_data['HostIp'],
host_port,
container_port)
actual_binds.append(':'.join(bind_def))
desired_binds = []
for container_port, bind_list in six.iteritems(data):
try:
if container_port.endswith('/tcp'):
container_port = container_port[:-4]
for bind_data in bind_list:
# Port range will have to be updated for future Docker
# versions (see
# https://github.com/docker/docker/issues/10220). Note
# that Docker 1.5.0 (released a few weeks after the fix
# was merged) does not appear to have this fix in it,
# so we're probably looking at 1.6.0 for this fix.
if bind_data['HostPort'] == '' or \
49153 <= int(bind_data['HostPort']) <= 65535:
except AttributeError:
# The port's protocol was not specified, so it is
# assumed to be TCP. Thus, according to docker-py usage
# examples, the port was passed as an int. Convert it
# to a string here.
container_port = str(container_port)
for bind_data in bind_list:
if isinstance(bind_data, tuple):
try:
host_ip, host_port = bind_data
host_port = str(host_port)
except ValueError:
host_ip = bind_data[0]
host_port = ''
else:
host_port = bind_data['HostPort']
if bind_data['HostIp'] in ('0.0.0.0', ''):
if host_port:
bind_def = (host_port, container_port)
else:
bind_def = (container_port,)
else:
bind_def = (bind_data['HostIp'],
host_port,
container_port)
actual_binds.append(':'.join(bind_def))
desired_binds = []
for container_port, bind_list in six.iteritems(data):
try:
if container_port.endswith('/tcp'):
container_port = container_port[:-4]
except AttributeError:
# The port's protocol was not specified, so it is
# assumed to be TCP. Thus, according to docker-py usage
# examples, the port was passed as an int. Convert it
# to a string here.
container_port = str(container_port)
for bind_data in bind_list:
if isinstance(bind_data, tuple):
try:
host_ip, host_port = bind_data
host_port = str(host_port)
except ValueError:
host_ip = bind_data[0]
host_port = ''
bind_def = '{0}:{1}:{2}'.format(
host_ip, host_port, container_port
bind_def = '{0}:{1}:{2}'.format(
host_ip, host_port, container_port
)
else:
if bind_data is not None:
bind_def = '{0}:{1}'.format(
bind_data, container_port
)
else:
if bind_data is not None:
bind_def = '{0}:{1}'.format(
bind_data, container_port
)
else:
bind_def = container_port
desired_binds.append(bind_def)
actual_binds.sort()
desired_binds.sort()
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_binds))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_binds))
if actual_binds != desired_binds:
ret.update({item: {'old': actual_binds,
'new': desired_binds}})
continue
elif item == 'links':
actual_links = []
for link in actual_data:
try:
link_name, alias_info = link.split(':')
except ValueError:
log.error(
'Failed to compare link {0}, unrecognized format'
.format(link)
)
continue
container_name, _, link_alias = alias_info.rpartition('/')
if not container_name:
log.error(
'Failed to interpret link alias from {0}, '
'unrecognized format'.format(alias_info)
)
continue
actual_links.append((link_name, link_alias))
actual_links.sort()
desired_links = sorted(data)
if actual_links != desired_links:
ret.update({item: {'old': actual_links,
'new': desired_links}})
continue
elif item == 'extra_hosts':
actual_hosts = sorted(actual_data)
desired_hosts = sorted(
['{0}:{1}'.format(x, y) for x, y in six.iteritems(data)]
)
if actual_hosts != desired_hosts:
ret.update({item: {'old': actual_hosts,
'new': desired_hosts}})
continue
elif isinstance(data, list):
# Compare two sorted lists of items. Won't work for "command"
# or "entrypoint" because those are both shell commands and the
# original order matters. It will, however, work for "volumes"
# because even though "volumes" is a sub-dict nested within the
# "actual" dict sorted(somedict) still just gives you a sorted
# list of the dictionary's keys. And we don't care about the
# value for "volumes", just its keys.
actual_data = sorted(actual_data)
desired_data = sorted(data)
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_data))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_data))
if actual_data != desired_data:
ret.update({item: {'old': actual_data,
'new': desired_data}})
bind_def = container_port
desired_binds.append(bind_def)
actual_binds.sort()
desired_binds.sort()
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_binds))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_binds))
if actual_binds != desired_binds:
ret.update({item: {'old': actual_binds,
'new': desired_binds}})
continue
else:
# Generic comparison, works on strings, numeric types, and
# booleans
if actual_data != data:
ret.update({item: {'old': actual_data, 'new': data}})
elif item == 'links':
actual_links = []
for link in actual_data:
try:
link_name, alias_info = link.split(':')
except ValueError:
log.error(
'Failed to compare link {0}, unrecognized format'
.format(link)
)
continue
container_name, _, link_alias = alias_info.rpartition('/')
if not container_name:
log.error(
'Failed to interpret link alias from {0}, '
'unrecognized format'.format(alias_info)
)
continue
actual_links.append((link_name, link_alias))
actual_links.sort()
desired_links = sorted(data)
if actual_links != desired_links:
ret.update({item: {'old': actual_links,
'new': desired_links}})
continue
elif item == 'extra_hosts':
actual_hosts = sorted(actual_data)
desired_hosts = sorted(
['{0}:{1}'.format(x, y) for x, y in six.iteritems(data)]
)
if actual_hosts != desired_hosts:
ret.update({item: {'old': actual_hosts,
'new': desired_hosts}})
continue
elif isinstance(data, list):
# Compare two sorted lists of items. Won't work for "command"
# or "entrypoint" because those are both shell commands and the
# original order matters. It will, however, work for "volumes"
# because even though "volumes" is a sub-dict nested within the
# "actual" dict sorted(somedict) still just gives you a sorted
# list of the dictionary's keys. And we don't care about the
# value for "volumes", just its keys.
actual_data = sorted(actual_data)
desired_data = sorted(data)
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_data))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_data))
if actual_data != desired_data:
ret.update({item: {'old': actual_data,
'new': desired_data}})
continue
else:
# Generic comparison, works on strings, numeric types, and
# booleans
if actual_data != data:
ret.update({item: {'old': actual_data, 'new': data}})
return ret
@ -1450,19 +1446,10 @@ def running(name,
# Strip __pub kwargs and divide the remaining arguments into the ones for
# container creation and the ones for starting containers.
invalid_kwargs = []
create_kwargs = {}
runtime_kwargs = {}
kwargs_copy = salt.utils.clean_kwargs(**copy.deepcopy(kwargs))
send_signal = kwargs_copy.pop('send_signal', False)
create_kwargs = salt.utils.clean_kwargs(**copy.deepcopy(kwargs))
send_signal = create_kwargs.pop('send_signal', False)
for key, val in six.iteritems(kwargs_copy):
if key in VALID_CREATE_OPTS:
create_kwargs[key] = val
elif key in VALID_RUNTIME_OPTS:
runtime_kwargs[key] = val
else:
invalid_kwargs.append(key)
invalid_kwargs = set(create_kwargs.keys()).difference(set(VALID_CREATE_OPTS.keys()))
if invalid_kwargs:
ret['comment'] = (
'The following arguments are invalid: {0}'
@ -1473,18 +1460,14 @@ def running(name,
# Input validation
try:
# Repack any dictlists that need it
_prep_input(runtime_kwargs)
_prep_input(create_kwargs)
# Perform data type validation and, where necessary, munge
# the data further so it is in a format that can be passed
# to dockerng.start.
_validate_input('runtime',
runtime_kwargs,
# to dockerng.create.
_validate_input(create_kwargs,
validate_ip_addrs=validate_ip_addrs)
# Add any needed container creation arguments based on the validated
# runtime arguments.
if runtime_kwargs.get('binds') is not None:
if create_kwargs.get('binds') is not None:
if 'volumes' not in create_kwargs:
# Check if there are preconfigured volumes in the image
for step in __salt__['dockerng.history'](image, quiet=True):
@ -1495,9 +1478,9 @@ def running(name,
# the ones from the "binds" configuration.
create_kwargs['volumes'] = [
x['bind']
for x in six.itervalues(runtime_kwargs['binds'])
for x in six.itervalues(create_kwargs['binds'])
]
if runtime_kwargs.get('port_bindings') is not None:
if create_kwargs.get('port_bindings') is not None:
if 'ports' not in create_kwargs:
# Check if there are preconfigured ports in the image
for step in __salt__['dockerng.history'](image, quiet=True):
@ -1507,14 +1490,8 @@ def running(name,
# No preconfigured ports, we need to make our own. Use
# the ones from the "port_bindings" configuration.
create_kwargs['ports'] = list(
runtime_kwargs['port_bindings'])
create_kwargs['port_bindings'])
# Perform data type validation and, where necessary, munge
# the data further so it is in a format that can be passed
# to dockerng.create.
_validate_input('create',
create_kwargs,
validate_ip_addrs=validate_ip_addrs)
except SaltInvocationError as exc:
ret['comment'] = '{0}'.format(exc)
return ret
@ -1540,9 +1517,7 @@ def running(name,
# Container is the correct image, let's check the container
# config and see if we need to replace the container
try:
changes_needed = _compare(pre_config,
create_kwargs,
runtime_kwargs)
changes_needed = _compare(pre_config, create_kwargs)
if changes_needed:
log.debug(
'dockerng.running: Analysis of container \'{0}\' '
@ -1634,6 +1609,7 @@ def running(name,
create_result = __salt__['dockerng.create'](
image,
name=name,
validate_ip_addrs=False,
# Already validated input
validate_input=False,
client_timeout=client_timeout,
@ -1653,10 +1629,6 @@ def running(name,
# Start container
__salt__['dockerng.start'](
name,
# Already validated input earlier, no need to repeat it
validate_ip_addrs=False,
validate_input=False,
**runtime_kwargs
)
except Exception as exc:
comments.append(
@ -1678,9 +1650,7 @@ def running(name,
if changes_needed:
try:
post_config = __salt__['dockerng.inspect_container'](name)
changes_still_needed = _compare(post_config,
create_kwargs,
runtime_kwargs)
changes_still_needed = _compare(post_config, create_kwargs)
if changes_still_needed:
log.debug(
'dockerng.running: Analysis of container \'{0}\' after '

View file

@ -66,13 +66,11 @@ class DockerngTestCase(TestCase):
'image:latest',
validate_input=False,
name='cont',
volumes=['/container-0'],
client_timeout=60)
dockerng_start.assert_called_with(
'cont',
binds={'/host-0': {'bind': '/container-0', 'ro': True}},
volumes=['/container-0'],
validate_ip_addrs=False,
validate_input=False)
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_with_predifined_volume(self):
'''
@ -103,13 +101,11 @@ class DockerngTestCase(TestCase):
dockerng_create.assert_called_with(
'image:latest',
validate_input=False,
name='cont',
client_timeout=60)
dockerng_start.assert_called_with(
'cont',
binds={'/host-0': {'bind': '/container-0', 'ro': True}},
validate_ip_addrs=False,
validate_input=False)
name='cont',
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_with_no_predifined_ports(self):
'''
@ -142,12 +138,10 @@ class DockerngTestCase(TestCase):
validate_input=False,
name='cont',
ports=[9797],
client_timeout=60)
dockerng_start.assert_called_with(
'cont',
port_bindings={9797: [9090]},
validate_ip_addrs=False,
validate_input=False)
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_with_predifined_ports(self):
'''
@ -179,12 +173,10 @@ class DockerngTestCase(TestCase):
'image:latest',
validate_input=False,
name='cont',
client_timeout=60)
dockerng_start.assert_called_with(
'cont',
port_bindings={9797: [9090]},
validate_ip_addrs=False,
validate_input=False)
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_compare_images_by_id(self):
'''
@ -456,6 +448,7 @@ class DockerngTestCase(TestCase):
dockerng_create.assert_called_with(
'image:latest',
validate_input=False,
validate_ip_addrs=False,
name='cont',
labels=['LABEL1', 'LABEL2'],
client_timeout=60)