mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #40481 from terminalmage/docker-auth-handling-2016.3
Backport auth and custom registry fixes from #40480 to 2016.3 branch
This commit is contained in:
commit
97caac4c0a
2 changed files with 332 additions and 425 deletions
|
@ -44,80 +44,66 @@ Docker_. docker-py can easily be installed using :py:func:`pip.install
|
|||
Authentication
|
||||
--------------
|
||||
|
||||
To push or pull images, credentials must be configured. Because a password must
|
||||
be used, it is recommended to place this configuration in :ref:`Pillar
|
||||
<pillar>` data. The configuration schema is as follows:
|
||||
If you have previously performed a ``docker login`` from the minion, then the
|
||||
credentials saved in ``~/.docker/config.json`` will be used for any actions
|
||||
which require authentication. If not, then credentials can be configured in
|
||||
Pillar data. The configuration schema is as follows:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
docker-registries:
|
||||
<registry_url>:
|
||||
email: <email_address>
|
||||
password: <password>
|
||||
username: <username>
|
||||
reauth: <boolean>
|
||||
password: <password>
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
docker-registries:
|
||||
https://index.docker.io/v1/:
|
||||
email: foo@foo.com
|
||||
password: s3cr3t
|
||||
hub:
|
||||
username: foo
|
||||
password: s3cr3t
|
||||
|
||||
Reauth is an optional parameter that forces the docker login to reauthorize using
|
||||
the credentials passed in the pillar data. Defaults to false.
|
||||
.. note::
|
||||
As of the 2016.3.7, 2016.11.4, and Nitrogen releases of Salt, credentials
|
||||
for the Docker Hub can be configured simply by specifying ``hub`` in place
|
||||
of the registry URL. In earlier releases, it is necessary to specify the
|
||||
actual registry URL for the Docker Hub (i.e.
|
||||
``https://index.docker.io/v1/``).
|
||||
|
||||
.. versionadded:: 2016.3.5,2016.11.1
|
||||
|
||||
For example:
|
||||
More than one registry can be configured. Salt will look for Docker credentials
|
||||
in the ``docker-registries`` Pillar key, as well as any key ending in
|
||||
``-docker-registries``. For example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
docker-registries:
|
||||
https://index.docker.io/v1/:
|
||||
email: foo@foo.com
|
||||
password: s3cr3t
|
||||
'https://mydomain.tld/registry:5000':
|
||||
username: foo
|
||||
reauth: True
|
||||
|
||||
Mulitiple registries can be configured. This can be done in one of two ways.
|
||||
The first way is to configure each registry under the ``docker-registries``
|
||||
pillar key.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
docker-registries:
|
||||
https://index.foo.io/v1/:
|
||||
email: foo@foo.com
|
||||
password: s3cr3t
|
||||
username: foo
|
||||
https://index.bar.io/v1/:
|
||||
email: foo@foo.com
|
||||
password: s3cr3t
|
||||
username: foo
|
||||
|
||||
The second way is to use separate pillar variables ending in
|
||||
``-docker-registries``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
foo-docker-registries:
|
||||
https://index.foo.io/v1/:
|
||||
email: foo@foo.com
|
||||
password: s3cr3t
|
||||
username: foo
|
||||
password: s3cr3t
|
||||
|
||||
bar-docker-registries:
|
||||
https://index.bar.io/v1/:
|
||||
email: foo@foo.com
|
||||
password: s3cr3t
|
||||
username: foo
|
||||
password: s3cr3t
|
||||
|
||||
To login to the configured registries, use the :py:func:`docker.login
|
||||
<salt.modules.dockermod.login>` function. This only needs to be done once for a
|
||||
given registry, and it will store/update the credentials in
|
||||
``~/.docker/config.json``.
|
||||
|
||||
.. note::
|
||||
For Salt releases before 2016.3.7 and 2016.11.4, :py:func:`docker.login
|
||||
<salt.modules.dockermod.login>` is not available. Instead, Salt will try to
|
||||
authenticate using each of your configured registries for each push/pull,
|
||||
behavior which is not correct and has been resolved in newer releases.
|
||||
|
||||
Both methods can be combined; any registry configured under
|
||||
``docker-registries`` or ``*-docker-registries`` will be detected.
|
||||
|
||||
Configuration Options
|
||||
---------------------
|
||||
|
@ -126,7 +112,8 @@ The following configuration options can be set to fine-tune how Salt uses
|
|||
Docker:
|
||||
|
||||
- ``docker.url``: URL to the docker service (default: local socket).
|
||||
- ``docker.version``: API version to use
|
||||
- ``docker.version``: API version to use (should not need to be set manually in
|
||||
the vast majority of cases)
|
||||
- ``docker.exec_driver``: Execution driver to use, one of ``nsenter``,
|
||||
``lxc-attach``, or ``docker-exec``. See the :ref:`Executing Commands Within a
|
||||
Running Container <docker-execution-driver>` section for more details on how
|
||||
|
@ -135,68 +122,6 @@ Docker:
|
|||
These configuration options are retrieved using :py:mod:`config.get
|
||||
<salt.modules.config.get>` (click the link for further information).
|
||||
|
||||
Functions
|
||||
---------
|
||||
|
||||
- Information Gathering
|
||||
- :py:func:`dockerng.depends <salt.modules.dockerng.depends>`
|
||||
- :py:func:`dockerng.diff <salt.modules.dockerng.diff>`
|
||||
- :py:func:`dockerng.exists <salt.modules.dockerng.exists>`
|
||||
- :py:func:`dockerng.history <salt.modules.dockerng.history>`
|
||||
- :py:func:`dockerng.images <salt.modules.dockerng.images>`
|
||||
- :py:func:`dockerng.info <salt.modules.dockerng.info>`
|
||||
- :py:func:`dockerng.inspect <salt.modules.dockerng.inspect>`
|
||||
- :py:func:`dockerng.inspect_container
|
||||
<salt.modules.dockerng.inspect_container>`
|
||||
- :py:func:`dockerng.inspect_image <salt.modules.dockerng.inspect_image>`
|
||||
- :py:func:`dockerng.list_containers
|
||||
<salt.modules.dockerng.list_containers>`
|
||||
- :py:func:`dockerng.list_tags <salt.modules.dockerng.list_tags>`
|
||||
- :py:func:`dockerng.logs <salt.modules.dockerng.logs>`
|
||||
- :py:func:`dockerng.pid <salt.modules.dockerng.pid>`
|
||||
- :py:func:`dockerng.port <salt.modules.dockerng.port>`
|
||||
- :py:func:`dockerng.ps <salt.modules.dockerng.ps>`
|
||||
- :py:func:`dockerng.state <salt.modules.dockerng.state>`
|
||||
- :py:func:`dockerng.search <salt.modules.dockerng.search>`
|
||||
- :py:func:`dockerng.top <salt.modules.dockerng.top>`
|
||||
- :py:func:`dockerng.version <salt.modules.dockerng.version>`
|
||||
- Container Management
|
||||
- :py:func:`dockerng.create <salt.modules.dockerng.create>`
|
||||
- :py:func:`dockerng.copy_from <salt.modules.dockerng.copy_from>`
|
||||
- :py:func:`dockerng.copy_to <salt.modules.dockerng.copy_to>`
|
||||
- :py:func:`dockerng.export <salt.modules.dockerng.export>`
|
||||
- :py:func:`dockerng.rm <salt.modules.dockerng.rm>`
|
||||
- Management of Container State
|
||||
- :py:func:`dockerng.kill <salt.modules.dockerng.kill>`
|
||||
- :py:func:`dockerng.pause <salt.modules.dockerng.pause>`
|
||||
- :py:func:`dockerng.restart <salt.modules.dockerng.restart>`
|
||||
- :py:func:`dockerng.start <salt.modules.dockerng.start>`
|
||||
- :py:func:`dockerng.stop <salt.modules.dockerng.stop>`
|
||||
- :py:func:`dockerng.unpause <salt.modules.dockerng.unpause>`
|
||||
- :py:func:`dockerng.wait <salt.modules.dockerng.wait>`
|
||||
- Image Management
|
||||
- :py:func:`dockerng.build <salt.modules.dockerng.build>`
|
||||
- :py:func:`dockerng.commit <salt.modules.dockerng.commit>`
|
||||
- :py:func:`dockerng.dangling <salt.modules.dockerng.dangling>`
|
||||
- :py:func:`dockerng.import <salt.modules.dockerng.import>`
|
||||
- :py:func:`dockerng.load <salt.modules.dockerng.load>`
|
||||
- :py:func:`dockerng.pull <salt.modules.dockerng.pull>`
|
||||
- :py:func:`dockerng.push <salt.modules.dockerng.push>`
|
||||
- :py:func:`dockerng.rmi <salt.modules.dockerng.rmi>`
|
||||
- :py:func:`dockerng.save <salt.modules.dockerng.save>`
|
||||
- :py:func:`dockerng.tag <salt.modules.dockerng.tag>`
|
||||
- Network Management
|
||||
- :py:func:`dockerng.networks <salt.modules.dockerng.networks>`
|
||||
- :py:func:`dockerng.create_network <salt.modules.dockerng.create_network>`
|
||||
- :py:func:`dockerng.remove_network <salt.modules.dockerng.remove_network>`
|
||||
- :py:func:`dockerng.inspect_network
|
||||
<salt.modules.dockerng.inspect_network>`
|
||||
- :py:func:`dockerng.connect_container_to_network
|
||||
<salt.modules.dockerng.connect_container_to_network>`
|
||||
- :py:func:`dockerng.disconnect_container_from_network
|
||||
<salt.modules.dockerng.disconnect_container_from_network>`
|
||||
|
||||
|
||||
|
||||
.. _docker-execution-driver:
|
||||
|
||||
|
@ -286,8 +211,6 @@ import time
|
|||
from salt.exceptions import CommandExecutionError, SaltInvocationError
|
||||
from salt.ext.six.moves import map # pylint: disable=import-error,redefined-builtin
|
||||
from salt.utils.args import get_function_argspec as _argspec
|
||||
from salt.utils.decorators \
|
||||
import identical_signature_wrapper as _mimic_signature
|
||||
import salt.utils
|
||||
|
||||
# Import 3rd-party libs
|
||||
|
@ -622,55 +545,55 @@ def _get_docker_py_versioninfo():
|
|||
pass
|
||||
|
||||
|
||||
def _get_client(**kwargs):
|
||||
client_kwargs = {}
|
||||
if 'client_timeout' in kwargs:
|
||||
client_kwargs['timeout'] = kwargs.pop('client_timeout')
|
||||
for key, val in (('base_url', 'docker.url'),
|
||||
('version', 'docker.version')):
|
||||
param = __salt__['config.get'](val, NOTSET)
|
||||
if param is not NOTSET:
|
||||
client_kwargs[key] = param
|
||||
|
||||
if 'base_url' not in client_kwargs and 'DOCKER_HOST' in os.environ:
|
||||
# Check if the DOCKER_HOST environment variable has been set
|
||||
client_kwargs['base_url'] = os.environ.get('DOCKER_HOST')
|
||||
|
||||
if 'version' not in client_kwargs:
|
||||
# Let docker-py auto detect docker version incase
|
||||
# it's not defined by user.
|
||||
client_kwargs['version'] = 'auto'
|
||||
|
||||
docker_machine = __salt__['config.get']('docker.machine', NOTSET)
|
||||
|
||||
if docker_machine is not NOTSET:
|
||||
docker_machine_json = __salt__['cmd.run'](
|
||||
['docker-machine', 'inspect', docker_machine],
|
||||
python_shell=False)
|
||||
try:
|
||||
docker_machine_json = json.loads(docker_machine_json)
|
||||
docker_machine_tls = \
|
||||
docker_machine_json['HostOptions']['AuthOptions']
|
||||
docker_machine_ip = docker_machine_json['Driver']['IPAddress']
|
||||
client_kwargs['base_url'] = \
|
||||
'https://' + docker_machine_ip + ':2376'
|
||||
client_kwargs['tls'] = docker.tls.TLSConfig(
|
||||
client_cert=(docker_machine_tls['ClientCertPath'],
|
||||
docker_machine_tls['ClientKeyPath']),
|
||||
ca_cert=docker_machine_tls['CaCertPath'],
|
||||
assert_hostname=False,
|
||||
verify=True)
|
||||
except Exception as exc:
|
||||
raise CommandExecutionError(
|
||||
'Docker machine {0} failed: {1}'.format(docker_machine, exc))
|
||||
try:
|
||||
# docker-py 2.0 renamed this client attribute
|
||||
return docker.APIClient(**client_kwargs)
|
||||
except AttributeError:
|
||||
return docker.Client(**client_kwargs)
|
||||
|
||||
|
||||
# Decorators
|
||||
class _api_version(object):
|
||||
'''
|
||||
Enforce a specific Docker Remote API version
|
||||
'''
|
||||
def __init__(self, api_version):
|
||||
self.api_version = api_version
|
||||
|
||||
def __call__(self, func):
|
||||
def wrapper(*args, **kwargs):
|
||||
'''
|
||||
Get the current client version and check it against the one passed
|
||||
'''
|
||||
_get_client()
|
||||
current_api_version = __context__['docker.client'].api_version
|
||||
if float(current_api_version) < self.api_version:
|
||||
raise CommandExecutionError(
|
||||
'This function requires a Docker API version of at least '
|
||||
'{0}. API version in use is {1}.'
|
||||
.format(self.api_version, current_api_version)
|
||||
)
|
||||
return func(*args, **salt.utils.clean_kwargs(**kwargs))
|
||||
return _mimic_signature(func, wrapper)
|
||||
|
||||
|
||||
class _client_version(object):
|
||||
'''
|
||||
Enforce a specific Docker client version
|
||||
'''
|
||||
def __init__(self, version):
|
||||
self.version = distutils.version.StrictVersion(version)
|
||||
|
||||
def __call__(self, func):
|
||||
def wrapper(*args, **kwargs):
|
||||
'''
|
||||
Get the current client version and check it against the one passed
|
||||
'''
|
||||
_get_client()
|
||||
current_version = '.'.join(map(str, _get_docker_py_versioninfo()))
|
||||
if distutils.version.StrictVersion(current_version) < self.version:
|
||||
raise CommandExecutionError(
|
||||
'This function requires a Docker Client version of at least '
|
||||
'{0}. Version in use is {1}.'
|
||||
.format(self.version, current_version)
|
||||
)
|
||||
return func(*args, **salt.utils.clean_kwargs(**kwargs))
|
||||
return _mimic_signature(func, wrapper)
|
||||
|
||||
|
||||
def _docker_client(wrapped):
|
||||
'''
|
||||
Decorator to run a function that requires the use of a docker.Client()
|
||||
|
@ -681,8 +604,8 @@ def _docker_client(wrapped):
|
|||
'''
|
||||
Ensure that the client is present
|
||||
'''
|
||||
client_timeout = __context__.get('docker.timeout', CLIENT_TIMEOUT)
|
||||
_get_client(timeout=client_timeout)
|
||||
if 'docker.client' not in __context__:
|
||||
__context__['docker.client'] = _get_client(**kwargs)
|
||||
return wrapped(*args, **salt.utils.clean_kwargs(**kwargs))
|
||||
return wrapper
|
||||
|
||||
|
@ -761,51 +684,6 @@ def _clear_context():
|
|||
pass
|
||||
|
||||
|
||||
def _get_client(timeout=None):
|
||||
'''
|
||||
Obtains a connection to a docker API (socket or URL) based on config.get
|
||||
mechanism (pillar -> grains)
|
||||
|
||||
By default it will use the base docker-py defaults which
|
||||
at the time of writing are using the local socket and
|
||||
the 1.4 API
|
||||
|
||||
Set those keys in your configuration tree somehow:
|
||||
|
||||
- docker.url: URL to the docker service
|
||||
- docker.version: API version to use (default: "auto")
|
||||
'''
|
||||
# In some edge cases, the client instance is missing attributes. Don't use
|
||||
# the cached client in those cases.
|
||||
if 'docker.client' not in __context__ \
|
||||
or not hasattr(__context__['docker.client'], 'timeout'):
|
||||
client_kwargs = {}
|
||||
for key, val in (('base_url', 'docker.url'),
|
||||
('version', 'docker.version')):
|
||||
param = __salt__['config.get'](val, NOTSET)
|
||||
if param is not NOTSET:
|
||||
client_kwargs[key] = param
|
||||
|
||||
if 'base_url' not in client_kwargs and 'DOCKER_HOST' in os.environ:
|
||||
# Check if the DOCKER_HOST environment variable has been set
|
||||
client_kwargs['base_url'] = os.environ.get('DOCKER_HOST')
|
||||
|
||||
if 'version' not in client_kwargs:
|
||||
# Let docker-py auto detect docker version incase
|
||||
# it's not defined by user.
|
||||
client_kwargs['version'] = 'auto'
|
||||
|
||||
try:
|
||||
# docker-py 2.0 renamed this client attribute
|
||||
__context__['docker.client'] = docker.APIClient(**client_kwargs)
|
||||
except AttributeError:
|
||||
__context__['docker.client'] = docker.Client(**client_kwargs)
|
||||
|
||||
# Set a new timeout if one was passed
|
||||
if timeout is not None and __context__['docker.client'].timeout != timeout:
|
||||
__context__['docker.client'].timeout = timeout
|
||||
|
||||
|
||||
def _get_md5(name, path):
|
||||
'''
|
||||
Get the MD5 checksum of a file from a container
|
||||
|
@ -825,33 +703,6 @@ def _get_exec_driver():
|
|||
Get the method to be used in shell commands
|
||||
'''
|
||||
contextkey = 'docker.exec_driver'
|
||||
'''
|
||||
docker-exec won't be used by default until we reach a version where it
|
||||
supports running commands as a user other than the effective user of the
|
||||
container.
|
||||
|
||||
See: https://groups.google.com/forum/#!topic/salt-users/i6Eq4rf5ml0
|
||||
|
||||
if contextkey in __context__:
|
||||
return __context__[contextkey]
|
||||
|
||||
from_config = __salt__['config.get'](contextkey, None)
|
||||
if from_config is not None:
|
||||
__context__[contextkey] = from_config
|
||||
else:
|
||||
_version = version()
|
||||
if 'VersionInfo' in _version:
|
||||
if _version['VersionInfo'] >= (1, 3, 0):
|
||||
__context__[contextkey] = 'docker-exec'
|
||||
elif distutils.version.LooseVersion(version()['Version']) \
|
||||
>= distutils.version.LooseVersion('1.3.0'):
|
||||
# LooseVersion is less preferable, but OK as a fallback.
|
||||
__context__[contextkey] = 'docker-exec'
|
||||
|
||||
# If the version_info tuple revealed a version < 1.3.0, the key will yet to
|
||||
# have been set in __context__, so we'll check if it's there yet and if
|
||||
# not, proceed with detecting execution driver from the output of info().
|
||||
''' # pylint: disable=pointless-string-statement
|
||||
if contextkey not in __context__:
|
||||
from_config = __salt__['config.get'](contextkey, None)
|
||||
# This if block can be removed once we make docker-exec a default
|
||||
|
@ -889,19 +740,24 @@ def _get_repo_tag(image, default_tag='latest'):
|
|||
'''
|
||||
Resolves the docker repo:tag notation and returns repo name and tag
|
||||
'''
|
||||
if ':' in image:
|
||||
if not isinstance(image, six.string_types):
|
||||
image = str(image)
|
||||
try:
|
||||
r_name, r_tag = image.rsplit(':', 1)
|
||||
if not r_tag:
|
||||
# Would happen if some wiseguy requests a tag ending in a colon
|
||||
# (e.g. 'somerepo:')
|
||||
log.warning(
|
||||
'Assuming tag \'{0}\' for repo \'{1}\''
|
||||
.format(default_tag, image)
|
||||
)
|
||||
r_tag = default_tag
|
||||
else:
|
||||
except ValueError:
|
||||
r_name = image
|
||||
r_tag = default_tag
|
||||
if not r_tag:
|
||||
# Would happen if some wiseguy requests a tag ending in a colon
|
||||
# (e.g. 'somerepo:')
|
||||
log.warning(
|
||||
'Assuming tag \'%s\' for repo \'%s\'', default_tag, image
|
||||
)
|
||||
r_tag = default_tag
|
||||
elif '/' in r_tag:
|
||||
# Public registry notation with no tag specified
|
||||
# (e.g. foo.bar.com:5000/imagename)
|
||||
return image, default_tag
|
||||
return r_name, r_tag
|
||||
|
||||
|
||||
|
@ -952,15 +808,19 @@ def _size_fmt(num):
|
|||
@_docker_client
|
||||
def _client_wrapper(attr, *args, **kwargs):
|
||||
'''
|
||||
Common functionality for getting information from a container
|
||||
Common functionality for running low-level API calls
|
||||
'''
|
||||
catch_api_errors = kwargs.pop('catch_api_errors', True)
|
||||
func = getattr(__context__['docker.client'], attr)
|
||||
func = getattr(__context__['docker.client'], attr, None)
|
||||
if func is None:
|
||||
raise SaltInvocationError('Invalid client action \'{0}\''.format(attr))
|
||||
err = ''
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
log.debug(
|
||||
'Attempting to run docker-py\'s "%s" function '
|
||||
'with args=%s and kwargs=%s', attr, args, kwargs
|
||||
)
|
||||
ret = func(*args, **kwargs)
|
||||
except docker.errors.APIError as exc:
|
||||
if catch_api_errors:
|
||||
# Generic handling of Docker API errors
|
||||
|
@ -971,77 +831,34 @@ def _client_wrapper(attr, *args, **kwargs):
|
|||
else:
|
||||
# Allow API errors to be caught further up the stack
|
||||
raise
|
||||
except docker.errors.DockerException as exc:
|
||||
# More general docker exception (catches InvalidVersion, etc.)
|
||||
raise CommandExecutionError(exc.__str__())
|
||||
except Exception as exc:
|
||||
err = '{0}'.format(exc)
|
||||
err = exc.__str__()
|
||||
else:
|
||||
if kwargs.get('stream', False):
|
||||
api_events = []
|
||||
try:
|
||||
for event in ret:
|
||||
api_events.append(json.loads(event))
|
||||
except Exception as exc:
|
||||
raise CommandExecutionError(
|
||||
'Unable to interpret API event: \'{0}\''.format(event),
|
||||
info={'Error': exc.__str__()}
|
||||
)
|
||||
return api_events
|
||||
else:
|
||||
return ret
|
||||
|
||||
# If we're here, it's because an exception was caught earlier, and the
|
||||
# API command failed.
|
||||
msg = 'Unable to perform {0}'.format(attr)
|
||||
if err:
|
||||
msg += ': {0}'.format(err)
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
|
||||
@_docker_client
|
||||
def _image_wrapper(attr, *args, **kwargs):
|
||||
'''
|
||||
Wrapper to run a docker-py function and return a list of dictionaries
|
||||
'''
|
||||
catch_api_errors = kwargs.pop('catch_api_errors', True)
|
||||
|
||||
if kwargs.pop('client_auth', False):
|
||||
# Set credentials
|
||||
registry_auth_config = __pillar__.get('docker-registries', {})
|
||||
for key, data in six.iteritems(__pillar__):
|
||||
if key.endswith('-docker-registries'):
|
||||
registry_auth_config.update(data)
|
||||
|
||||
err = (
|
||||
'{0} Docker credentials{1}. Please see the dockerng remote '
|
||||
'execution module documentation for information on how to '
|
||||
'configure authentication.'
|
||||
)
|
||||
try:
|
||||
for registry, creds in six.iteritems(registry_auth_config):
|
||||
__context__['docker.client'].login(
|
||||
creds['username'],
|
||||
password=creds['password'],
|
||||
email=creds.get('email'),
|
||||
registry=registry,
|
||||
reauth=creds.get('reauth', False))
|
||||
except KeyError:
|
||||
raise SaltInvocationError(
|
||||
err.format('Incomplete', ' for registry {0}'.format(registry))
|
||||
)
|
||||
client_timeout = kwargs.pop('client_timeout', None)
|
||||
if client_timeout is not None:
|
||||
__context__['docker.client'].timeout = client_timeout
|
||||
|
||||
func = getattr(__context__['docker.client'], attr)
|
||||
if func is None:
|
||||
raise SaltInvocationError('Invalid client action \'{0}\''.format(attr))
|
||||
ret = []
|
||||
try:
|
||||
output = func(*args, **kwargs)
|
||||
if not kwargs.get('stream', False):
|
||||
output = output.splitlines()
|
||||
for line in output:
|
||||
ret.append(json.loads(line))
|
||||
except docker.errors.APIError as exc:
|
||||
if catch_api_errors:
|
||||
# Generic handling of Docker API errors
|
||||
raise CommandExecutionError(
|
||||
'Error {0}: {1}'.format(exc.response.status_code,
|
||||
exc.explanation)
|
||||
)
|
||||
else:
|
||||
# Allow API errors to be caught further up the stack
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise CommandExecutionError(
|
||||
'Error occurred performing docker {0}: {1}'.format(attr, exc)
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _build_status(data, item):
|
||||
'''
|
||||
Process a status update from a docker build, updating the data structure
|
||||
|
@ -1926,6 +1743,98 @@ def _validate_input(kwargs,
|
|||
pass
|
||||
|
||||
|
||||
def login(*registries):
|
||||
'''
|
||||
.. versionadded:: 2016.3.7,2016.11.4,Nitrogen
|
||||
|
||||
Performs a ``docker login`` to authenticate to one or more configured
|
||||
repositories. See the documentation at the top of this page to configure
|
||||
authentication credentials.
|
||||
|
||||
Multiple registry URLs (matching those configured in Pillar) can be passed,
|
||||
and Salt will attempt to login to *just* those registries. If no registry
|
||||
URLs are provided, Salt will attempt to login to *all* configured
|
||||
registries.
|
||||
|
||||
**RETURN DATA**
|
||||
|
||||
A dictionary containing the following keys:
|
||||
|
||||
- ``Results`` - A dictionary mapping registry URLs to the authentication
|
||||
result. ``True`` means a successful login, ``False`` means a failed
|
||||
login.
|
||||
- ``Errors`` - A list of errors encountered during the course of this
|
||||
function.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion docker.login
|
||||
salt myminion docker.login hub
|
||||
salt myminion docker.login hub https://mydomain.tld/registry/
|
||||
'''
|
||||
# NOTE: This function uses the "docker login" CLI command so that login
|
||||
# information is added to the config.json, since docker-py isn't designed
|
||||
# to do so.
|
||||
registry_auth = __pillar__.get('docker-registries', {})
|
||||
ret = {}
|
||||
errors = ret.setdefault('Errors', [])
|
||||
if not isinstance(registry_auth, dict):
|
||||
errors.append('\'docker-registries\' Pillar value must be a dictionary')
|
||||
registry_auth = {}
|
||||
for key, data in six.iteritems(__pillar__):
|
||||
try:
|
||||
if key.endswith('-docker-registries'):
|
||||
try:
|
||||
registry_auth.update(data)
|
||||
except TypeError:
|
||||
errors.append(
|
||||
'\'{0}\' Pillar value must be a dictionary'.format(key)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# If no registries passed, we will auth to all of them
|
||||
if not registries:
|
||||
registries = list(registry_auth)
|
||||
|
||||
results = ret.setdefault('Results', {})
|
||||
for registry in registries:
|
||||
if registry not in registry_auth:
|
||||
errors.append(
|
||||
'No match found for registry \'{0}\''.format(registry)
|
||||
)
|
||||
continue
|
||||
try:
|
||||
username = registry_auth[registry]['username']
|
||||
password = registry_auth[registry]['password']
|
||||
except TypeError:
|
||||
errors.append(
|
||||
'Invalid configuration for registry \'{0}\''.format(registry)
|
||||
)
|
||||
except KeyError as exc:
|
||||
errors.append(
|
||||
'Missing {0} for registry \'{1}\''.format(exc, registry)
|
||||
)
|
||||
else:
|
||||
cmd = ['docker', 'login', '-u', username, '-p', password]
|
||||
if registry.lower() != 'hub':
|
||||
cmd.append(registry)
|
||||
login_cmd = __salt__['cmd.run_all'](
|
||||
cmd,
|
||||
python_shell=False,
|
||||
output_loglevel='quiet',
|
||||
)
|
||||
results[registry] = login_cmd['retcode'] == 0
|
||||
if not results[registry]:
|
||||
if login_cmd['stderr']:
|
||||
errors.append(login_cmd['stderr'])
|
||||
elif login_cmd['stdout']:
|
||||
errors.append(login_cmd['stdout'])
|
||||
return ret
|
||||
|
||||
|
||||
# Functions for information gathering
|
||||
def depends(name):
|
||||
'''
|
||||
|
@ -2199,7 +2108,6 @@ def images(verbose=False, **kwargs):
|
|||
return ret
|
||||
|
||||
|
||||
@_docker_client
|
||||
def info():
|
||||
'''
|
||||
Returns a dictionary of system-wide information. Equivalent to running
|
||||
|
@ -3061,7 +2969,7 @@ def create(image,
|
|||
# API v1.15 introduced HostConfig parameter
|
||||
# https://docs.docker.com/engine/reference/api/docker_remote_api_v1.15/#create-a-container
|
||||
if salt.utils.version_cmp(version()['ApiVersion'], '1.15') > 0:
|
||||
client = __context__['docker.client']
|
||||
client = _get_client()
|
||||
host_config_args = get_client_args()['host_config']
|
||||
create_kwargs['host_config'] = client.create_host_config(
|
||||
**dict((arg, create_kwargs.pop(arg, None)) for arg in host_config_args if arg != 'version')
|
||||
|
@ -3770,10 +3678,10 @@ def import_(source,
|
|||
path = __salt__['container_resource.cache_file'](source)
|
||||
|
||||
time_started = time.time()
|
||||
response = _image_wrapper('import_image',
|
||||
path,
|
||||
repository=repo_name,
|
||||
tag=repo_tag)
|
||||
response = _client_wrapper('import_image',
|
||||
path,
|
||||
repository=repo_name,
|
||||
tag=repo_tag)
|
||||
ret = {'Time_Elapsed': time.time() - time_started}
|
||||
_clear_context()
|
||||
|
||||
|
@ -3930,8 +3838,7 @@ def pull(image,
|
|||
api_response=False,
|
||||
client_timeout=CLIENT_TIMEOUT):
|
||||
'''
|
||||
Pulls an image from a Docker registry. See the documentation at the top of
|
||||
this page to configure authenticated access.
|
||||
Pulls an image from a Docker registry
|
||||
|
||||
image
|
||||
Image to be pulled, in ``repo:tag`` notation. If just the repository
|
||||
|
@ -3980,13 +3887,12 @@ def pull(image,
|
|||
repo_name, repo_tag = _get_repo_tag(image)
|
||||
kwargs = {'tag': repo_tag,
|
||||
'stream': True,
|
||||
'client_auth': True,
|
||||
'client_timeout': client_timeout}
|
||||
if insecure_registry:
|
||||
kwargs['insecure_registry'] = insecure_registry
|
||||
|
||||
time_started = time.time()
|
||||
response = _image_wrapper('pull', repo_name, **kwargs)
|
||||
response = _client_wrapper('pull', repo_name, **kwargs)
|
||||
ret = {'Time_Elapsed': time.time() - time_started}
|
||||
_clear_context()
|
||||
|
||||
|
@ -4034,7 +3940,7 @@ def push(image,
|
|||
This is due to changes in the Docker Remote API.
|
||||
|
||||
Pushes an image to a Docker registry. See the documentation at top of this
|
||||
page to configure authenticated access.
|
||||
page to configure authentication credentials.
|
||||
|
||||
image
|
||||
Image to be pushed, in ``repo:tag`` notation.
|
||||
|
@ -4085,13 +3991,12 @@ def push(image,
|
|||
|
||||
kwargs = {'tag': repo_tag,
|
||||
'stream': True,
|
||||
'client_auth': True,
|
||||
'client_timeout': client_timeout}
|
||||
if insecure_registry:
|
||||
kwargs['insecure_registry'] = insecure_registry
|
||||
|
||||
time_started = time.time()
|
||||
response = _image_wrapper('push', repo_name, **kwargs)
|
||||
response = _client_wrapper('push', repo_name, **kwargs)
|
||||
ret = {'Time_Elapsed': time.time() - time_started}
|
||||
_clear_context()
|
||||
|
||||
|
@ -4115,6 +4020,8 @@ def push(image,
|
|||
elif item_type == 'errorDetail':
|
||||
_error_detail(errors, item)
|
||||
|
||||
if errors:
|
||||
ret['Errors'] = errors
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -4167,18 +4074,19 @@ def rmi(*names, **kwargs):
|
|||
catch_api_errors=False)
|
||||
except docker.errors.APIError as exc:
|
||||
if exc.response.status_code == 409:
|
||||
err = ('Unable to remove image \'{0}\' because it is in '
|
||||
'use by '.format(name))
|
||||
errors.append(exc.explanation)
|
||||
deps = depends(name)
|
||||
if deps['Containers']:
|
||||
err += 'container(s): {0}'.format(
|
||||
', '.join(deps['Containers'])
|
||||
)
|
||||
if deps['Images']:
|
||||
if deps['Containers'] or deps['Images']:
|
||||
err = 'Image is in use by '
|
||||
if deps['Containers']:
|
||||
err += ' and '
|
||||
err += 'image(s): {0}'.format(', '.join(deps['Images']))
|
||||
errors.append(err)
|
||||
err += 'container(s): {0}'.format(
|
||||
', '.join(deps['Containers'])
|
||||
)
|
||||
if deps['Images']:
|
||||
if deps['Containers']:
|
||||
err += ' and '
|
||||
err += 'image(s): {0}'.format(', '.join(deps['Images']))
|
||||
errors.append(err)
|
||||
else:
|
||||
errors.append('Error {0}: {1}'.format(exc.response.status_code,
|
||||
exc.explanation))
|
||||
|
@ -4426,11 +4334,8 @@ def tag_(name, image, force=False):
|
|||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
# Network Management
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def networks(names=None, ids=None):
|
||||
'''
|
||||
List existing networks
|
||||
|
@ -4457,8 +4362,6 @@ def networks(names=None, ids=None):
|
|||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def create_network(name, driver=None):
|
||||
'''
|
||||
Create a new network
|
||||
|
@ -4481,8 +4384,6 @@ def create_network(name, driver=None):
|
|||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def remove_network(network_id):
|
||||
'''
|
||||
Remove a network
|
||||
|
@ -4502,8 +4403,6 @@ def remove_network(network_id):
|
|||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def inspect_network(network_id):
|
||||
'''
|
||||
Inspect Network
|
||||
|
@ -4523,8 +4422,6 @@ def inspect_network(network_id):
|
|||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def connect_container_to_network(container, network_id):
|
||||
'''
|
||||
Connect container to network.
|
||||
|
@ -4549,8 +4446,6 @@ def connect_container_to_network(container, network_id):
|
|||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def disconnect_container_from_network(container, network_id):
|
||||
'''
|
||||
Disconnect container from network.
|
||||
|
@ -4577,8 +4472,6 @@ def disconnect_container_from_network(container, network_id):
|
|||
# Volume Management
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def volumes(filters=None):
|
||||
'''
|
||||
List existing volumes
|
||||
|
@ -4600,8 +4493,6 @@ def volumes(filters=None):
|
|||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def create_volume(name, driver=None, driver_opts=None):
|
||||
'''
|
||||
Create a new volume
|
||||
|
@ -4630,8 +4521,6 @@ def create_volume(name, driver=None, driver_opts=None):
|
|||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def remove_volume(name):
|
||||
'''
|
||||
Remove a volume
|
||||
|
@ -4653,8 +4542,6 @@ def remove_volume(name):
|
|||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def inspect_volume(name):
|
||||
'''
|
||||
Inspect Volume
|
||||
|
@ -4707,7 +4594,6 @@ def kill(name):
|
|||
|
||||
|
||||
@_refresh_mine_cache
|
||||
@_api_version(1.12)
|
||||
@_ensure_exists
|
||||
def pause(name):
|
||||
'''
|
||||
|
@ -4902,7 +4788,6 @@ def stop(name, timeout=STOP_TIMEOUT, **kwargs):
|
|||
|
||||
|
||||
@_refresh_mine_cache
|
||||
@_api_version(1.12)
|
||||
@_ensure_exists
|
||||
def unpause(name):
|
||||
'''
|
||||
|
|
|
@ -23,7 +23,6 @@ ensure_in_syspath('../../')
|
|||
from salt.modules import dockerng as dockerng_mod
|
||||
from salt.exceptions import CommandExecutionError, SaltInvocationError
|
||||
|
||||
dockerng_mod.__context__ = {'docker.docker_version': ''}
|
||||
dockerng_mod.__salt__ = {}
|
||||
|
||||
|
||||
|
@ -36,17 +35,25 @@ class DockerngTestCase(TestCase):
|
|||
'''
|
||||
Validate dockerng module
|
||||
'''
|
||||
def setUp(self):
|
||||
'''
|
||||
Ensure we aren't persisting context dunders between tests
|
||||
'''
|
||||
dockerng_mod.__context__ = {'docker.docker_version': ''}
|
||||
|
||||
def test_ps_with_host_true(self):
|
||||
'''
|
||||
Check that dockerng.ps called with host is ``True``,
|
||||
include resutlt of ``network.interfaces`` command in returned result.
|
||||
'''
|
||||
client = Mock()
|
||||
client.containers = MagicMock(return_value=[])
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
network_interfaces = Mock(return_value={'mocked': None})
|
||||
with patch.dict(dockerng_mod.__salt__,
|
||||
{'network.interfaces': network_interfaces}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': MagicMock()}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
ret = dockerng_mod.ps_(host=True)
|
||||
self.assertEqual(ret,
|
||||
{'host': {'interfaces': {'mocked': None}}})
|
||||
|
@ -55,9 +62,11 @@ class DockerngTestCase(TestCase):
|
|||
'''
|
||||
Check that dockerng.ps accept filters parameter.
|
||||
'''
|
||||
client = MagicMock()
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
client = Mock()
|
||||
client.containers = MagicMock(return_value=[])
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.ps_(filters={'label': 'KEY'})
|
||||
client.containers.assert_called_once_with(
|
||||
all=True,
|
||||
|
@ -84,14 +93,15 @@ class DockerngTestCase(TestCase):
|
|||
):
|
||||
mine_send = Mock()
|
||||
command = getattr(dockerng_mod, command_name)
|
||||
docker_client = MagicMock()
|
||||
docker_client.api_version = '1.12'
|
||||
client = MagicMock()
|
||||
client.api_version = '1.12'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__salt__,
|
||||
{'mine.send': mine_send,
|
||||
'container_resource.run': MagicMock(),
|
||||
'cp.cache_file': MagicMock(return_value=False)}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': docker_client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
command('container', *args)
|
||||
mine_send.assert_called_with('dockerng.ps', verbose=True, all=True,
|
||||
host=True)
|
||||
|
@ -114,10 +124,11 @@ class DockerngTestCase(TestCase):
|
|||
client.api_version = '1.19'
|
||||
client.create_host_config.return_value = host_config
|
||||
client.create_container.return_value = {}
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create('image', cmd='ls', name='ctn')
|
||||
client.create_container.assert_called_once_with(
|
||||
command='ls',
|
||||
|
@ -143,10 +154,11 @@ class DockerngTestCase(TestCase):
|
|||
client.api_version = '1.19'
|
||||
client.create_host_config.return_value = host_config
|
||||
client.create_container.return_value = {}
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create('image', name='ctn', publish_all_ports=True)
|
||||
client.create_container.assert_called_once_with(
|
||||
host_config=host_config,
|
||||
|
@ -171,10 +183,11 @@ class DockerngTestCase(TestCase):
|
|||
client.api_version = '1.19'
|
||||
client.create_host_config.return_value = host_config
|
||||
client.create_container.return_value = {}
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
|
@ -206,10 +219,10 @@ class DockerngTestCase(TestCase):
|
|||
client.api_version = '1.19'
|
||||
client.create_host_config.return_value = host_config
|
||||
client.create_container.return_value = {}
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
|
@ -241,10 +254,11 @@ class DockerngTestCase(TestCase):
|
|||
client.api_version = '1.19'
|
||||
client.create_host_config.return_value = host_config
|
||||
client.create_container.return_value = {}
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
self.assertRaises(SaltInvocationError,
|
||||
dockerng_mod.create,
|
||||
'image',
|
||||
|
@ -271,10 +285,11 @@ class DockerngTestCase(TestCase):
|
|||
client.api_version = '1.19'
|
||||
client.create_host_config.return_value = host_config
|
||||
client.create_container.return_value = {}
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
|
@ -301,10 +316,11 @@ class DockerngTestCase(TestCase):
|
|||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.networks(
|
||||
names=['foo'],
|
||||
ids=['01234'],
|
||||
|
@ -327,18 +343,13 @@ class DockerngTestCase(TestCase):
|
|||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.create_network(
|
||||
'foo',
|
||||
driver='bridge',
|
||||
)
|
||||
client.create_network.assert_called_once_with(
|
||||
'foo',
|
||||
driver='bridge',
|
||||
)
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create_network('foo', driver='bridge')
|
||||
client.create_network.assert_called_once_with('foo', driver='bridge')
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
|
@ -353,10 +364,11 @@ class DockerngTestCase(TestCase):
|
|||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.remove_network('foo')
|
||||
client.remove_network.assert_called_once_with('foo')
|
||||
|
||||
|
@ -373,10 +385,11 @@ class DockerngTestCase(TestCase):
|
|||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.inspect_network('foo')
|
||||
client.inspect_network.assert_called_once_with('foo')
|
||||
|
||||
|
@ -393,10 +406,11 @@ class DockerngTestCase(TestCase):
|
|||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.connect_container_to_network('container', 'foo')
|
||||
client.connect_container_to_network.assert_called_once_with(
|
||||
'container', 'foo')
|
||||
|
@ -414,10 +428,11 @@ class DockerngTestCase(TestCase):
|
|||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.disconnect_container_from_network('container', 'foo')
|
||||
client.disconnect_container_from_network.assert_called_once_with(
|
||||
'container', 'foo')
|
||||
|
@ -434,16 +449,13 @@ class DockerngTestCase(TestCase):
|
|||
}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.volumes(
|
||||
filters={'dangling': [True]},
|
||||
)
|
||||
client.volumes.assert_called_once_with(
|
||||
filters={'dangling': [True]},
|
||||
)
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.volumes(filters={'dangling': [True]})
|
||||
client.volumes.assert_called_once_with(filters={'dangling': [True]})
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
|
@ -457,10 +469,11 @@ class DockerngTestCase(TestCase):
|
|||
}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create_volume(
|
||||
'foo',
|
||||
driver='bridge',
|
||||
|
@ -484,10 +497,11 @@ class DockerngTestCase(TestCase):
|
|||
}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.remove_volume('foo')
|
||||
client.remove_volume.assert_called_once_with('foo')
|
||||
|
||||
|
@ -503,10 +517,11 @@ class DockerngTestCase(TestCase):
|
|||
}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.inspect_volume('foo')
|
||||
client.inspect_volume.assert_called_once_with('foo')
|
||||
|
||||
|
@ -514,13 +529,14 @@ class DockerngTestCase(TestCase):
|
|||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
client.wait = Mock(return_value=0)
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
dockerng_inspect_container = Mock(side_effect=[
|
||||
{'State': {'Running': True}},
|
||||
{'State': {'Stopped': True}}])
|
||||
with patch.object(dockerng_mod, 'inspect_container',
|
||||
dockerng_inspect_container):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod._clear_context()
|
||||
result = dockerng_mod.wait('foo')
|
||||
self.assertEqual(result, {'result': True,
|
||||
|
@ -532,14 +548,15 @@ class DockerngTestCase(TestCase):
|
|||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
client.wait = Mock(return_value=0)
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
dockerng_inspect_container = Mock(side_effect=[
|
||||
{'State': {'Stopped': True}},
|
||||
{'State': {'Stopped': True}},
|
||||
])
|
||||
with patch.object(dockerng_mod, 'inspect_container',
|
||||
dockerng_inspect_container):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod._clear_context()
|
||||
result = dockerng_mod.wait('foo')
|
||||
self.assertEqual(result, {'result': False,
|
||||
|
@ -552,14 +569,15 @@ class DockerngTestCase(TestCase):
|
|||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
client.wait = Mock(return_value=0)
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
dockerng_inspect_container = Mock(side_effect=[
|
||||
{'State': {'Stopped': True}},
|
||||
{'State': {'Stopped': True}},
|
||||
])
|
||||
with patch.object(dockerng_mod, 'inspect_container',
|
||||
dockerng_inspect_container):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod._clear_context()
|
||||
result = dockerng_mod.wait('foo', ignore_already_stopped=True)
|
||||
self.assertEqual(result, {'result': True,
|
||||
|
@ -571,11 +589,12 @@ class DockerngTestCase(TestCase):
|
|||
def test_wait_success_absent_container(self):
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
dockerng_inspect_container = Mock(side_effect=CommandExecutionError)
|
||||
|
||||
with patch.object(dockerng_mod, 'inspect_container',
|
||||
dockerng_inspect_container):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod._clear_context()
|
||||
result = dockerng_mod.wait('foo', ignore_already_stopped=True)
|
||||
self.assertEqual(result, {'result': True,
|
||||
|
@ -585,13 +604,14 @@ class DockerngTestCase(TestCase):
|
|||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
client.wait = Mock(return_value=1)
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
dockerng_inspect_container = Mock(side_effect=[
|
||||
{'State': {'Running': True}},
|
||||
{'State': {'Stopped': True}}])
|
||||
|
||||
with patch.object(dockerng_mod, 'inspect_container',
|
||||
dockerng_inspect_container):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod._clear_context()
|
||||
result = dockerng_mod.wait('foo', fail_on_exit_status=True)
|
||||
self.assertEqual(result, {'result': False,
|
||||
|
@ -603,13 +623,14 @@ class DockerngTestCase(TestCase):
|
|||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
client.wait = Mock(return_value=1)
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
dockerng_inspect_container = Mock(side_effect=[
|
||||
{'State': {'Stopped': True}},
|
||||
{'State': {'Stopped': True}}])
|
||||
|
||||
with patch.object(dockerng_mod, 'inspect_container',
|
||||
dockerng_inspect_container):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod._clear_context()
|
||||
result = dockerng_mod.wait('foo',
|
||||
ignore_already_stopped=True,
|
||||
|
@ -632,8 +653,9 @@ class DockerngTestCase(TestCase):
|
|||
{'Id': 'sha256:abcdef'},
|
||||
{'Id': 'sha256:abcdefg',
|
||||
'RepoTags': ['image:latest']}])
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
get_client_mock = MagicMock(return_value=client)
|
||||
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod._clear_context()
|
||||
result = dockerng_mod.images()
|
||||
self.assertEqual(result,
|
||||
|
|
Loading…
Add table
Reference in a new issue