mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Merge remote-tracking branch 'upstream/2016.3' into merge-2016.3-2016.11
This commit is contained in:
commit
a6d68f50fe
@ -9,5 +9,7 @@ External Logging Handlers
|
||||
:toctree:
|
||||
:template: autosummary.rst.tmpl
|
||||
|
||||
fluent_mod
|
||||
log4mongo_mod
|
||||
logstash_mod
|
||||
sentry_mod
|
||||
sentry_mod
|
||||
|
@ -0,0 +1,5 @@
|
||||
============================
|
||||
salt.log.handlers.fluent_mod
|
||||
============================
|
||||
|
||||
.. automodule:: salt.log.handlers.fluent_mod
|
@ -0,0 +1,5 @@
|
||||
===============================
|
||||
salt.log.handlers.log4mongo_mod
|
||||
===============================
|
||||
|
||||
.. automodule:: salt.log.handlers.log4mongo_mod
|
@ -1 +1,5 @@
|
||||
.. automodule:: salt.log.handlers.logstash_mod
|
||||
==============================
|
||||
salt.log.handlers.logstash_mod
|
||||
==============================
|
||||
|
||||
.. automodule:: salt.log.handlers.logstash_mod
|
||||
|
@ -1 +1,5 @@
|
||||
.. automodule:: salt.log.handlers.sentry_mod
|
||||
============================
|
||||
salt.log.handlers.sentry_mod
|
||||
============================
|
||||
|
||||
.. automodule:: salt.log.handlers.sentry_mod
|
||||
|
@ -1680,6 +1680,29 @@ def _validate_opts(opts):
|
||||
return True
|
||||
|
||||
|
||||
def _validate_ssh_minion_opts(opts):
|
||||
'''
|
||||
Ensure we're not using any invalid ssh_minion_opts. We want to make sure
|
||||
that the ssh_minion_opts does not override any pillar or fileserver options
|
||||
inherited from the master config. To add other items, modify the if
|
||||
statement in the for loop below.
|
||||
'''
|
||||
ssh_minion_opts = opts.get('ssh_minion_opts', {})
|
||||
if not isinstance(ssh_minion_opts, dict):
|
||||
log.error('Invalidly-formatted ssh_minion_opts')
|
||||
opts.pop('ssh_minion_opts')
|
||||
|
||||
for opt_name in list(ssh_minion_opts):
|
||||
if re.match('^[a-z0-9]+fs_', opt_name, flags=re.IGNORECASE) \
|
||||
or 'pillar' in opt_name \
|
||||
or opt_name in ('fileserver_backend',):
|
||||
log.warning(
|
||||
'\'%s\' is not a valid ssh_minion_opts parameter, ignoring',
|
||||
opt_name
|
||||
)
|
||||
ssh_minion_opts.pop(opt_name)
|
||||
|
||||
|
||||
def _append_domain(opts):
|
||||
'''
|
||||
Append a domain to the existing id if it doesn't already exist
|
||||
@ -3269,6 +3292,7 @@ def master_config(path, env_var='SALT_MASTER_CONFIG', defaults=None, exit_on_con
|
||||
overrides.update(include_config(include, path, verbose=True),
|
||||
exit_on_config_errors=exit_on_config_errors)
|
||||
opts = apply_master_config(overrides, defaults)
|
||||
_validate_ssh_minion_opts(opts)
|
||||
_validate_opts(opts)
|
||||
# If 'nodegroups:' is uncommented in the master config file, and there are
|
||||
# no nodegroups defined, opts['nodegroups'] will be None. Fix this by
|
||||
|
@ -44,85 +44,66 @@ Docker_. docker-py can easily be installed using :py:func:`pip.install
|
||||
Authentication
|
||||
--------------
|
||||
|
||||
To push or pull images, credentials must be configured. By default dockerng will
|
||||
try to get the credentials from the default docker auth file, located under the
|
||||
home directory of the user running the salt-minion (HOME/.docker/config.json).
|
||||
Because a password must be used, it is recommended to place this configuration
|
||||
in :ref:`Pillar <pillar>` data. If pillar data specifies a registry already
|
||||
present in the default docker auth file, it will override.
|
||||
|
||||
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
|
||||
---------------------
|
||||
@ -131,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
|
||||
@ -140,72 +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>`
|
||||
- Salt Functions and States Execution
|
||||
- :py:func:`dockerng.call <salt.modules.dockerng.call>`
|
||||
- :py:func:`dockerng.sls <salt.modules.dockerng.sls>`
|
||||
- :py:func:`dockerng.sls_build <salt.modules.dockerng.sls_build>`
|
||||
|
||||
|
||||
.. _docker-execution-driver:
|
||||
|
||||
Executing Commands Within a Running Container
|
||||
@ -655,60 +571,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 salt.utils.decorators.identical_signature_wrapper(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:
|
||||
error_message = (
|
||||
'This function requires a Docker Client version of at least '
|
||||
'{0}. Version in use is {1}.'
|
||||
.format(self.version, current_version))
|
||||
minion_conf = __salt__['config.get']('docker.version', NOTSET)
|
||||
if minion_conf is not NOTSET:
|
||||
error_message += (
|
||||
' Hint: Your minion configuration specified'
|
||||
' `docker.version` = "{0}"'.format(minion_conf))
|
||||
raise CommandExecutionError(error_message)
|
||||
return func(*args, **salt.utils.clean_kwargs(**kwargs))
|
||||
return salt.utils.decorators.identical_signature_wrapper(func, wrapper)
|
||||
|
||||
|
||||
def _docker_client(wrapped):
|
||||
'''
|
||||
Decorator to run a function that requires the use of a docker.Client()
|
||||
@ -719,8 +630,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
|
||||
|
||||
@ -799,70 +710,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'
|
||||
|
||||
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)
|
||||
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
|
||||
__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
|
||||
@ -882,33 +729,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
|
||||
@ -946,19 +766,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
|
||||
|
||||
|
||||
@ -1009,15 +834,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
|
||||
@ -1028,107 +857,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):
|
||||
# Get credential from the home directory of the user running
|
||||
# salt-minion, default auth file for docker (~/.docker/config.json)
|
||||
registry_auth_config = {}
|
||||
try:
|
||||
home = os.path.expanduser("~")
|
||||
docker_auth_file = os.path.join(home, '.docker', 'config.json')
|
||||
with salt.utils.fopen(docker_auth_file) as fp:
|
||||
try:
|
||||
docker_auth = json.load(fp)
|
||||
fp.close()
|
||||
except (OSError, IOError) as exc:
|
||||
if exc.errno != errno.ENOENT:
|
||||
log.error('Failed to read docker auth file %s: %s', docker_auth_file, exc)
|
||||
docker_auth = {}
|
||||
if isinstance(docker_auth, dict):
|
||||
if 'auths' in docker_auth and isinstance(docker_auth['auths'], dict):
|
||||
for key, data in six.iteritems(docker_auth['auths']):
|
||||
if isinstance(data, dict):
|
||||
email = str(data.get('email', ''))
|
||||
b64_auth = base64.b64decode(data.get('auth', ''))
|
||||
username, password = b64_auth.split(':')
|
||||
registry = 'https://{registry}'.format(registry=key)
|
||||
registry_auth_config.update({registry: {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'email': email
|
||||
}})
|
||||
except Exception as e:
|
||||
log.debug('dockerng was unable to load credential from ~/.docker/config.json'
|
||||
' trying with pillar now ({0})'.format(e))
|
||||
# Set credentials from pillar - Overwrite auth from config.json
|
||||
registry_auth_config.update(__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
|
||||
@ -2055,6 +1811,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):
|
||||
'''
|
||||
@ -2328,7 +2176,6 @@ def images(verbose=False, **kwargs):
|
||||
return ret
|
||||
|
||||
|
||||
@_docker_client
|
||||
def info():
|
||||
'''
|
||||
Returns a dictionary of system-wide information. Equivalent to running
|
||||
@ -3208,7 +3055,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')
|
||||
@ -3928,10 +3775,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()
|
||||
|
||||
@ -4088,8 +3935,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
|
||||
@ -4138,13 +3984,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()
|
||||
|
||||
@ -4192,7 +4037,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.
|
||||
@ -4243,13 +4088,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()
|
||||
|
||||
@ -4273,6 +4117,8 @@ def push(image,
|
||||
elif item_type == 'errorDetail':
|
||||
_error_detail(errors, item)
|
||||
|
||||
if errors:
|
||||
ret['Errors'] = errors
|
||||
return ret
|
||||
|
||||
|
||||
@ -4325,18 +4171,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))
|
||||
@ -4584,11 +4431,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
|
||||
@ -4615,8 +4459,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
|
||||
@ -4639,8 +4481,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
|
||||
@ -4660,8 +4500,6 @@ def remove_network(network_id):
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def inspect_network(network_id):
|
||||
'''
|
||||
Inspect Network
|
||||
@ -4681,8 +4519,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.
|
||||
@ -4707,8 +4543,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.
|
||||
@ -4735,8 +4569,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
|
||||
@ -4758,8 +4590,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
|
||||
@ -4788,8 +4618,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
|
||||
@ -4811,8 +4639,6 @@ def remove_volume(name):
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def inspect_volume(name):
|
||||
'''
|
||||
Inspect Volume
|
||||
@ -4865,7 +4691,6 @@ def kill(name):
|
||||
|
||||
|
||||
@_refresh_mine_cache
|
||||
@_api_version(1.12)
|
||||
@_ensure_exists
|
||||
def pause(name):
|
||||
'''
|
||||
@ -5060,7 +4885,6 @@ def stop(name, timeout=STOP_TIMEOUT, **kwargs):
|
||||
|
||||
|
||||
@_refresh_mine_cache
|
||||
@_api_version(1.12)
|
||||
@_ensure_exists
|
||||
def unpause(name):
|
||||
'''
|
||||
|
@ -12,7 +12,6 @@ Provides the service module for systemd
|
||||
'''
|
||||
# Import python libs
|
||||
from __future__ import absolute_import
|
||||
import copy
|
||||
import errno
|
||||
import glob
|
||||
import logging
|
||||
@ -125,8 +124,7 @@ def _clear_context():
|
||||
# raise a RuntimeError.
|
||||
for key in list(__context__):
|
||||
try:
|
||||
if key.startswith('systemd._systemctl_status.') \
|
||||
or key in ('systemd.systemd_services',):
|
||||
if key.startswith('systemd._systemctl_status.'):
|
||||
__context__.pop(key)
|
||||
except AttributeError:
|
||||
continue
|
||||
@ -178,9 +176,6 @@ def _get_systemd_services():
|
||||
'''
|
||||
Use os.listdir() to get all the unit files
|
||||
'''
|
||||
contextkey = 'systemd.systemd_services'
|
||||
if contextkey in __context__:
|
||||
return __context__[contextkey]
|
||||
ret = set()
|
||||
for path in SYSTEM_CONFIG_PATHS + (LOCAL_CONFIG_PATH,):
|
||||
# Make sure user has access to the path, and if the path is a link
|
||||
@ -194,11 +189,10 @@ def _get_systemd_services():
|
||||
continue
|
||||
if unit_type in VALID_UNIT_TYPES:
|
||||
ret.add(unit_name if unit_type == 'service' else fullname)
|
||||
__context__[contextkey] = copy.deepcopy(ret)
|
||||
return ret
|
||||
|
||||
|
||||
def _get_sysv_services():
|
||||
def _get_sysv_services(systemd_services=None):
|
||||
'''
|
||||
Use os.listdir() and os.access() to get all the initscripts
|
||||
'''
|
||||
@ -220,7 +214,9 @@ def _get_sysv_services():
|
||||
)
|
||||
return []
|
||||
|
||||
systemd_services = _get_systemd_services()
|
||||
if systemd_services is None:
|
||||
systemd_services = _get_systemd_services()
|
||||
|
||||
ret = []
|
||||
for sysv_service in sysv_services:
|
||||
if os.access(os.path.join(INITSCRIPT_PATH, sysv_service), os.X_OK):
|
||||
@ -537,7 +533,7 @@ def get_all():
|
||||
salt '*' service.get_all
|
||||
'''
|
||||
ret = _get_systemd_services()
|
||||
ret.update(set(_get_sysv_services()))
|
||||
ret.update(set(_get_sysv_services(systemd_services=ret)))
|
||||
return sorted(ret)
|
||||
|
||||
|
||||
|
@ -14,6 +14,7 @@ from __future__ import absolute_import
|
||||
|
||||
# Import python libs
|
||||
import copy
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@ -1589,6 +1590,7 @@ def latest(name,
|
||||
return _uptodate(ret, target, _format_comments(comments))
|
||||
else:
|
||||
if os.path.isdir(target):
|
||||
target_contents = os.listdir(target)
|
||||
if force_clone:
|
||||
# Clone is required, and target directory exists, but the
|
||||
# ``force`` option is enabled, so we need to clear out its
|
||||
@ -1607,22 +1609,28 @@ def latest(name,
|
||||
'place (force_clone=True set in git.latest state)'
|
||||
.format(target, name)
|
||||
)
|
||||
try:
|
||||
if os.path.islink(target):
|
||||
os.unlink(target)
|
||||
else:
|
||||
salt.utils.rm_rf(target)
|
||||
except OSError as exc:
|
||||
removal_errors = {}
|
||||
for target_object in target_contents:
|
||||
target_path = os.path.join(target, target_object)
|
||||
try:
|
||||
salt.utils.rm_rf(target_path)
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.ENOENT:
|
||||
removal_errors[target_path] = exc
|
||||
if removal_errors:
|
||||
err_strings = [
|
||||
' {0}\n {1}'.format(k, v)
|
||||
for k, v in six.iteritems(removal_errors)
|
||||
]
|
||||
return _fail(
|
||||
ret,
|
||||
'Unable to remove {0}: {1}'.format(target, exc),
|
||||
'Unable to remove\n{0}'.format('\n'.join(err_strings)),
|
||||
comments
|
||||
)
|
||||
else:
|
||||
ret['changes']['forced clone'] = True
|
||||
ret['changes']['forced clone'] = True
|
||||
# Clone is required, but target dir exists and is non-empty. We
|
||||
# can't proceed.
|
||||
elif os.listdir(target):
|
||||
elif target_contents:
|
||||
return _fail(
|
||||
ret,
|
||||
'Target \'{0}\' exists, is non-empty and is not a git '
|
||||
|
@ -25,7 +25,6 @@ ensure_in_syspath('../../')
|
||||
import salt.modules.dockerng as dockerng_mod
|
||||
from salt.exceptions import CommandExecutionError, SaltInvocationError
|
||||
|
||||
dockerng_mod.__context__ = {}
|
||||
dockerng_mod.__salt__ = {}
|
||||
dockerng_mod.__opts__ = {}
|
||||
|
||||
@ -36,49 +35,30 @@ class DockerngTestCase(TestCase):
|
||||
'''
|
||||
Validate dockerng module
|
||||
'''
|
||||
def setUp(self):
|
||||
'''
|
||||
Ensure we aren't persisting context dunders between tests
|
||||
'''
|
||||
dockerng_mod.__context__ = {'docker.docker_version': ''}
|
||||
|
||||
try:
|
||||
docker_version = dockerng_mod.docker.version_info
|
||||
except AttributeError:
|
||||
docker_version = 0,
|
||||
|
||||
client_args_mock = MagicMock(return_value={
|
||||
'create_container': [
|
||||
'image', 'command', 'hostname', 'user', 'detach', 'stdin_open',
|
||||
'tty', 'ports', 'environment', 'volumes', 'network_disabled',
|
||||
'name', 'entrypoint', 'working_dir', 'domainname', 'cpuset',
|
||||
'host_config', 'mac_address', 'labels', 'volume_driver',
|
||||
'stop_signal', 'networking_config', 'healthcheck',
|
||||
'stop_timeout'],
|
||||
'host_config': [
|
||||
'binds', 'port_bindings', 'lxc_conf', 'publish_all_ports',
|
||||
'links', 'privileged', 'dns', 'dns_search', 'volumes_from',
|
||||
'network_mode', 'restart_policy', 'cap_add', 'cap_drop',
|
||||
'devices', 'extra_hosts', 'read_only', 'pid_mode', 'ipc_mode',
|
||||
'security_opt', 'ulimits', 'log_config', 'mem_limit',
|
||||
'memswap_limit', 'mem_reservation', 'kernel_memory',
|
||||
'mem_swappiness', 'cgroup_parent', 'group_add', 'cpu_quota',
|
||||
'cpu_period', 'blkio_weight', 'blkio_weight_device',
|
||||
'device_read_bps', 'device_write_bps', 'device_read_iops',
|
||||
'device_write_iops', 'oom_kill_disable', 'shm_size', 'sysctls',
|
||||
'tmpfs', 'oom_score_adj', 'dns_opt', 'cpu_shares',
|
||||
'cpuset_cpus', 'userns_mode', 'pids_limit', 'isolation',
|
||||
'auto_remove', 'storage_opt'],
|
||||
'networking_config': [
|
||||
'aliases', 'links', 'ipv4_address', 'ipv6_address',
|
||||
'link_local_ips'],
|
||||
})
|
||||
|
||||
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}}})
|
||||
@ -87,9 +67,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,
|
||||
@ -115,15 +97,16 @@ 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.object(dockerng_mod, 'get_client_args', self.client_args_mock):
|
||||
with patch.dict(dockerng_mod.__context__, {'docker.client': docker_client}):
|
||||
command('container', *args)
|
||||
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)
|
||||
|
||||
@ -145,10 +128,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.object(dockerng_mod, 'get_client_args', self.client_args_mock):
|
||||
with patch.dict(dockerng_mod.__context__, {'docker.client': client}):
|
||||
dockerng_mod.create('image', cmd='ls', name='ctn')
|
||||
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',
|
||||
host_config=host_config,
|
||||
@ -173,10 +157,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.object(dockerng_mod, 'get_client_args', self.client_args_mock):
|
||||
with patch.dict(dockerng_mod.__context__, {'docker.client': client}):
|
||||
dockerng_mod.create('image', name='ctn', publish_all_ports=True)
|
||||
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,
|
||||
image='image',
|
||||
@ -201,15 +186,16 @@ 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.object(dockerng_mod, 'get_client_args', self.client_args_mock):
|
||||
with patch.dict(dockerng_mod.__context__, {'docker.client': client}):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
labels={'KEY': 'VALUE'},
|
||||
validate_input=True,
|
||||
)
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
labels={'KEY': 'VALUE'},
|
||||
validate_input=True,
|
||||
)
|
||||
client.create_container.assert_called_once_with(
|
||||
labels={'KEY': 'VALUE'},
|
||||
host_config=host_config,
|
||||
@ -236,15 +222,15 @@ 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.object(dockerng_mod, 'get_client_args', self.client_args_mock):
|
||||
with patch.dict(dockerng_mod.__context__, {'docker.client': client}):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
labels=['KEY1', 'KEY2'],
|
||||
validate_input=True,
|
||||
)
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
labels=['KEY1', 'KEY2'],
|
||||
validate_input=True,
|
||||
)
|
||||
client.create_container.assert_called_once_with(
|
||||
labels=['KEY1', 'KEY2'],
|
||||
host_config=host_config,
|
||||
@ -271,10 +257,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',
|
||||
@ -302,15 +289,16 @@ 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.object(dockerng_mod, 'get_client_args', self.client_args_mock):
|
||||
with patch.dict(dockerng_mod.__context__, {'docker.client': client}):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
labels=[{'KEY1': 'VALUE1'}, {'KEY2': 'VALUE2'}],
|
||||
validate_input=True,
|
||||
)
|
||||
with patch.object(dockerng_mod, '_get_client', get_client_mock):
|
||||
dockerng_mod.create(
|
||||
'image',
|
||||
name='ctn',
|
||||
labels=[{'KEY1': 'VALUE1'}, {'KEY2': 'VALUE2'}],
|
||||
validate_input=True,
|
||||
)
|
||||
client.create_container.assert_called_once_with(
|
||||
labels={'KEY1': 'VALUE1', 'KEY2': 'VALUE2'},
|
||||
host_config=host_config,
|
||||
@ -333,10 +321,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'],
|
||||
@ -361,18 +350,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_version < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
@ -389,10 +373,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')
|
||||
|
||||
@ -411,10 +396,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')
|
||||
|
||||
@ -433,10 +419,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')
|
||||
@ -456,10 +443,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')
|
||||
@ -478,16 +466,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_version < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
@ -503,10 +488,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',
|
||||
@ -532,10 +518,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')
|
||||
|
||||
@ -553,10 +540,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')
|
||||
|
||||
@ -564,13 +552,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,
|
||||
@ -582,14 +571,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,
|
||||
@ -602,14 +592,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,
|
||||
@ -621,11 +612,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,
|
||||
@ -635,13 +627,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,
|
||||
@ -653,13 +646,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,
|
||||
@ -799,8 +793,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,
|
||||
|
@ -168,8 +168,8 @@ class SystemdTestCase(TestCase):
|
||||
'''
|
||||
listdir_mock = MagicMock(side_effect=[
|
||||
['foo.service', 'multi-user.target.wants', 'mytimer.timer'],
|
||||
[],
|
||||
['foo.service', 'multi-user.target.wants', 'bar.service'],
|
||||
['mysql', 'nginx', 'README'],
|
||||
['mysql', 'nginx', 'README']
|
||||
])
|
||||
access_mock = MagicMock(
|
||||
|
Loading…
Reference in New Issue
Block a user